Merge remote-tracking branch 'upstream/master' into spaupdate03

This commit is contained in:
KayenEQ
2021-07-30 13:51:16 -04:00
28 changed files with 591 additions and 84 deletions
+1 -1
View File
@@ -13,4 +13,4 @@ steps:
image: akkadius/eqemu-server:latest
commands:
- sudo chown eqemu:eqemu /drone/src/ * -R
- git submodule init && git submodule update && mkdir -p build && cd build && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_BOTS=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' .. && make -j$((`nproc`-4))
- git submodule init && git submodule update && mkdir -p build && cd build && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_ENABLE_BOTS=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' .. && make -j$((`nproc`-4))
+44
View File
@@ -131,6 +131,47 @@ OPTION(EQEMU_BUILD_TESTS "Build utility tests." OFF)
OPTION(EQEMU_BUILD_CLIENT_FILES "Build Client Import/Export Data Programs." ON)
OPTION(EQEMU_PREFER_LUA "Build with normal Lua even if LuaJIT is found." OFF)
#PRNG options
OPTION(EQEMU_ADDITIVE_LFIB_PRNG "Use Additive LFib for PRNG." OFF)
MARK_AS_ADVANCED(EQEMU_ADDITIVE_LFIB_PRNG)
OPTION(EQEMU_BIASED_INT_DIST "Use biased int dist instead of uniform." OFF)
MARK_AS_ADVANCED(EQEMU_BIASED_INT_DIST)
SET(EQEMU_CUSTOM_PRNG_ENGINE "" CACHE STRING "Custom random engine. (ex. std::default_random_engine)")
MARK_AS_ADVANCED(EQEMU_CUSTOM_PRNG_ENGINE)
IF(CMAKE_COMPILER_IS_GNUCXX)
OPTION(EQEMU_SFMT19937 "Use GCC's extention for SIMD Fast MT19937." OFF)
MARK_AS_ADVANCED(EQEMU_SFMT19937)
ENDIF()
IF(EQEMU_ADDITIVE_LFIB_PRNG)
ADD_DEFINITIONS(-DUSE_ADDITIVE_LFIB_PRNG)
IF(EQEMU_SFMT19937)
MESSAGE(STATUS "SFMT19937 and ADDITITVE_LFIB_PRNG both set, SFMT19937 ignored.")
SET(EQEMU_SFMT19937 OFF)
ENDIF()
IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "")
MESSAGE(STATUS "CUSTOM_PRNG_ENGINE and ADDITITVE_LFIB_PRNG both set, CUSTOM_PRNG_ENGINE ignored.")
SET(EQEMU_CUSTOM_PRNG_ENGINE "")
ENDIF()
ENDIF()
IF(EQEMU_SFMT19937)
ADD_DEFINITIONS(-DUSE_SFMT19937)
IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "")
MESSAGE(STATUS "CUSTOM_PRNG_ENGINE and SFMT19937 both set, CUSTOM_PRNG_ENGINE ignored.")
SET(EQEMU_CUSTOM_PRNG_ENGINE "")
ENDIF()
ENDIF()
IF(NOT EQEMU_CUSTOM_PRNG_ENGINE STREQUAL "")
ADD_DEFINITIONS(-DUSE_CUSTOM_PRNG_ENGINE=${EQEMU_CUSTOM_PRNG_ENGINE})
ENDIF()
IF(EQEMU_BIASED_INT_DIST)
ADD_DEFINITIONS(-DBIASED_INT_DIST)
ENDIF()
IF(EQEMU_COMMANDS_LOGGING)
ADD_DEFINITIONS(-DCOMMANDS_LOGGING)
ENDIF(EQEMU_COMMANDS_LOGGING)
@@ -198,6 +239,9 @@ ELSEIF(OpenSSL_FOUND)
SET(TLS_LIBRARY_LIBS ${OPENSSL_LIBRARIES})
SET(TLS_LIBRARY_INCLUDE ${OPENSSL_INCLUDE_DIR})
ADD_DEFINITIONS(-DEQEMU_USE_OPENSSL)
IF(${OPENSSL_VERSION} VERSION_GREATER_EQUAL "1.1.1")
ADD_DEFINITIONS(-DCPPHTTPLIB_OPENSSL_SUPPORT)
ENDIF()
ELSEIF(MBEDTLS_FOUND)
SET(TLS_LIBRARY_TYPE " mbedTLS")
SET(TLS_LIBRARY_ENABLED ON)
+1
View File
@@ -451,6 +451,7 @@ SET(repositories
)
SET(common_headers
additive_lagged_fibonacci_engine.h
any.h
base_packet.h
base_data.h
+147
View File
@@ -0,0 +1,147 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2021 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <cstdint>
#include <limits>
#include <type_traits>
#include <algorithm>
#include <iostream>
/*
* This is an additive lagged fibonacci generator as seen in The Art of Computer Programming, Vol. 2
* This should roughly match the implementation that EQ's client uses and be compatible with our Random class
*
* EQ's rand looks like it was from an example implementation that as posted on pscode.com
*
* You might also want to consider defining BIASED_INT_DIST as well to more closely match EQ
*/
namespace EQ {
template<typename UIntType, size_t w, size_t j, size_t k>
class additive_lagged_fibonacci_engine {
static_assert(std::is_unsigned<UIntType>::value, "result_type must be an unsigned integral type");
static_assert(0u < j && j < k, "0 < j < k");
static_assert(0u < w && w <= std::numeric_limits<UIntType>::digits,
"template argument substituting w out of bounds");
public:
using result_type = UIntType;
static constexpr size_t word_size = w;
static constexpr size_t short_lag = j;
static constexpr size_t long_lag = k;
static constexpr result_type default_seed = 19780503u; // default for subtract_with_carry_engine
additive_lagged_fibonacci_engine() : additive_lagged_fibonacci_engine(default_seed) {}
explicit additive_lagged_fibonacci_engine(result_type sd) { seed(sd); }
void seed(result_type seed = default_seed)
{
state1 = long_lag - long_lag;
state2 = long_lag - short_lag;
state[0] = static_cast<int>(seed) & ((1u << word_size) - 1);
state[1] = 1;
for (int i = 2; i < long_lag; ++i)
state[i] = (state[i - 1] + state[i - 2]) & ((1u << word_size) - 1);
return;
}
// TODO: seed via seed_seq
static constexpr result_type min() { return 0; }
static constexpr result_type max() { return ((1u << word_size) - 1) >> 6; }
void discard(unsigned long long z) {
for (; z != 0ULL; --z)
(*this)();
}
result_type operator()() {
result_type rand = (state[state1] + state[state2]) & ((1u << word_size) - 1);
state[state1] = rand;
if (++state1 == long_lag)
state1 = 0;
if (++state2 == long_lag)
state2 = 0;
return rand >> 6;
}
private:
result_type state1;
result_type state2;
result_type state[long_lag];
public:
template<typename UInt, size_t W, size_t J, size_t K>
friend bool operator==(const additive_lagged_fibonacci_engine<UInt, W, J, K> &x,
const additive_lagged_fibonacci_engine<UInt, W, J, K> &y)
{
return std::equal(x.state, x.state + long_lag, y.state) && x.state1 == y.state1 &&
x.state2 == y.state2;
}
template<typename UInt, size_t W, size_t J, size_t K>
friend bool operator!=(const additive_lagged_fibonacci_engine<UInt, W, J, K> &x,
const additive_lagged_fibonacci_engine<UInt, W, J, K> &y)
{ return !(x == y); }
template<typename UInt, size_t W, size_t J, size_t K, typename CharT, typename Traits>
friend std::basic_ostream<CharT, Traits> &
operator<<(std::basic_istream<CharT, Traits> &os, additive_lagged_fibonacci_engine<UInt, W, J, K> &x)
{
using ios_base = typename std::basic_istream<CharT, Traits>::ios_base;
const typename ios_base::fmtflags flags = os.flags();
const CharT fill = os.fill();
const CharT space = os.widen(' ');
os.flags(ios_base::dec | ios_base::fixed | ios_base::left);
os.fill(space);
for (size_t i = 0; i < long_lag; ++i)
os << x.state[i] << space;
os << x.state1 << space << x.state2;
os.flags(flags);
os.fill(fill);
return os;
}
template<typename UInt, size_t W, size_t J, size_t K, typename CharT, typename Traits>
friend std::basic_istream<CharT, Traits> &
operator>>(std::basic_istream<CharT, Traits> &is, additive_lagged_fibonacci_engine<UInt, W, J, K> &x)
{
using ios_base = typename std::basic_istream<CharT, Traits>::ios_base;
const typename ios_base::fmtflags flags = is.flags();
is.flags(ios_base::dec | ios_base::skipws);
for (size_t i = 0; i < long_lag; ++i)
is >> x.state[i];
is >> x.state1;
is >> x.state2;
is.flags(flags);
return is;
}
};
using EQRand = additive_lagged_fibonacci_engine<uint32_t, 30, 24, 55>;
};
+39 -7
View File
@@ -1,5 +1,5 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net)
Copyright (C) 2001-2021 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -25,10 +25,24 @@
#include <iterator>
#include <type_traits>
/* This uses mt19937 seeded with the std::random_device
* The idea is to have this be included as a member of another class
* so mocking out for testing is easier
* If you need to reseed random.Reseed()
#ifdef USE_SFMT19937
// only GCC supports, so lets turn it off
#ifndef __GNUC__
#undef USE_SFMT19937
#endif
#ifdef __clang__
#undef USE_SFMT19937
#endif
#endif
#ifdef USE_ADDITIVE_LFIB_PRNG
#include "additive_lagged_fibonacci_engine.h"
#elif defined(USE_SFMT19937)
#include <ext/random>
#endif
/* This uses mt19937 (or other compatible engine) seeded with the std::random_device. The idea is to have this be
* included as a member of another class so mocking out for testing is easier If you need to reseed random.Reseed()
* Eventually this should be derived from an abstract base class
*/
@@ -40,7 +54,12 @@ namespace EQ {
{
if (low > high)
std::swap(low, high);
// EQ uses biased int distribution, so I guess we can support it :P
#ifdef BIASED_INT_DIST
return low + m_gen() % (high - low + 1);
#else
return int_dist(m_gen, int_param_t(low, high)); // [low, high]
#endif
}
// AKA old MakeRandomFloat
@@ -98,11 +117,24 @@ namespace EQ {
}
private:
#ifndef BIASED_INT_DIST
typedef std::uniform_int_distribution<int>::param_type int_param_t;
typedef std::uniform_real_distribution<double>::param_type real_param_t;
std::mt19937 m_gen;
std::uniform_int_distribution<int> int_dist;
#endif
typedef std::uniform_real_distribution<double>::param_type real_param_t;
std::uniform_real_distribution<double> real_dist;
// define USE_CUSTOM_PRNG_ENGINE to any supported random engine like:
// #define USE_CUSTOM_PRNG_ENGINE std::default_random_engine
#ifdef USE_ADDITIVE_LFIB_PRNG
EQRand
#elif defined(USE_SFMT19937)
__gnu_cxx::sfmt19937
#elif defined(USE_CUSTOM_PRNG_ENGINE)
USE_CUSTOM_PRNG_ENGINE
#else
std::mt19937
#endif
m_gen;
};
}
+9 -4
View File
@@ -609,7 +609,7 @@ typedef enum {
#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_ExtraAttackChance 266 // implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 2-handed primary weapon, base: chance, limit: amt attacks, max: none
#define SE_AddPetCommand 267 // implemented - sets command base2 to base1
#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.
@@ -825,11 +825,16 @@ typedef enum {
#define SE_Ff_Value_Min 479 // implemented, @Ff, Minimum base value of a spell that can be focused, base: spells to be focused base1 value
#define SE_Ff_Value_Max 480 // implemented, @Ff, Max base value of a spell that can be focused, base: spells to be focused base1 value
#define SE_Fc_Cast_Spell_On_Land 481 // implemented, @Fc, On Target, cast spell if hit by spell, base: chance pct, limit: spellid
//#define SE_Skill_Base_Damage_Mod 482 //
#define SE_Fc_Spell_Damage_Pct_IncomingPC 483 // implemented, @Fc, On Target, spell damage taken mod pct, base: min pct, limit: max pct
#define SE_Fc_Spell_Damage_Amt_IncomingPC 484 // implemented, @Fc, On Target, damage taken flat amt, base: amt
#define SE_Ff_CasterClass 485 // implemented, @Ff, Caster of spell on target with a focus effect that is checked by incoming spells must be specified class(es). base1: class(es), Note: Set multiple classes same as would for items
#define SE_Ff_Same_Caster 486 // implemented, @Ff, Caster of spell on target with a focus effect that is checked by incoming spells, base1: 0=Must be different caster 1=Must be same caster
#define SE_Fc_Cast_Spell_On_Land 481 // Implemented - [FOCUS] Spells cast on target with this Focus Effect will have chance to cause a Spell to be cast if limits met.
#define SE_Skill_Base_Damage_Mod 482 // implemented, @OffBonus, modify base melee damage by percent, base: pct, limit: skill(-1=ALL), max: none
#define SE_Fc_Spell_Damage_Pct_IncomingPC 483 // Implemented - [FOCUS] modifies incoming spell damage by percent
#define SE_Fc_Spell_Damage_Amt_IncomingPC 484 // Implemented - [FOCUS] modifies incoming spell damage by flat amount. Typically adds damage to incoming spells.
#define SE_Ff_CasterClass 485 // Implemented - [FOCUS LIMIT] Caster of spell on target with a focus effect that is checked by incoming spells must be specified class.
#define SE_Ff_Same_Caster 486 // Implemented - [FOCUS LIMIT] Caster of spell on target with a focus effect that is checked by incoming spells 0=Must be different caster 1=Must be same caster
//#define SE_Extend_Tradeskill_Cap 487 //
//#define SE_Defender_Melee_Force_Pct_PC 488 //
//#define SE_Worn_Endurance_Regen_Cap 489 //
@@ -841,8 +846,8 @@ typedef enum {
#define SE_Ff_DurationMax 495 // implemented, @Ff, Max duration of spell that can be focused, base: tics
#define SE_Critical_Melee_Damage_Mod_Max 496 // implemented - increase or decrease by percent critical damage (not stackable)
//#define SE_Ff_FocusCastProcNoBypass 497 //
//#define SE_AddExtraAttackPct_1h_Primary 498 //
//#define SE_AddExtraAttackPct_1h_Secondary 499 //
#define SE_AddExtraAttackPct_1h_Primary 498 // implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 1-handed primary weapon, base: chance, limit: amt attacks, max: none
#define SE_AddExtraAttackPct_1h_Secondary 499 //implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 1-handed secondary weapon, base: chance, limit: amt attacks, max: none
#define SE_Fc_CastTimeMod2 500 // implemented, @Fc, On Caster, cast time mod pct, base: pct, Note: Can reduce to instant cast
#define SE_Fc_CastTimeAmt 501 // implemented, @Fc, On Caster, cast time mod flat amt, base: milliseconds, Note: Can reduce to instant cast
#define SE_Fearstun 502 // implemented - Stun with a max level limit. Normal stun restrictions don't apply.
+33 -6
View File
@@ -5483,13 +5483,40 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
CheckIncreaseSkill(EQ::skills::SkillDoubleAttack, target, -10);
if (CheckDoubleAttack()) {
Attack(target, hand, false, false, IsFromSpell);
// Modern AA description: Increases your chance of ... performing one additional hit with a 2-handed weapon when double attacking by 2%.
if (hand == EQ::invslot::slotPrimary) {
auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance +
itembonuses.ExtraAttackChance;
if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance))
Attack(target, hand, false, false, IsFromSpell);
if (HasTwoHanderEquipped()) {
auto extraattackchance = aabonuses.ExtraAttackChance[0] + spellbonuses.ExtraAttackChance[0] +
itembonuses.ExtraAttackChance[0];
if (extraattackchance && zone->random.Roll(extraattackchance)) {
auto extraattackamt = std::max({ aabonuses.ExtraAttackChance[1], spellbonuses.ExtraAttackChance[1], itembonuses.ExtraAttackChance[1] });
for (int i = 0; i < extraattackamt; i++) {
Attack(target, hand, false, false, IsFromSpell);
}
}
}
else {
auto extraattackchance_primary = aabonuses.ExtraAttackChancePrimary[0] + spellbonuses.ExtraAttackChancePrimary[0] +
itembonuses.ExtraAttackChancePrimary[0];
if (extraattackchance_primary && zone->random.Roll(extraattackchance_primary)) {
auto extraattackamt_primary = std::max({ aabonuses.ExtraAttackChancePrimary[1], spellbonuses.ExtraAttackChancePrimary[1], itembonuses.ExtraAttackChancePrimary[1] });
for (int i = 0; i < extraattackamt_primary; i++) {
Attack(target, hand, false, false, IsFromSpell);
}
}
}
}
if (hand == EQ::invslot::slotSecondary) {
auto extraattackchance_secondary = aabonuses.ExtraAttackChanceSecondary[0] + spellbonuses.ExtraAttackChanceSecondary[0] +
itembonuses.ExtraAttackChanceSecondary[0];
if (extraattackchance_secondary && zone->random.Roll(extraattackchance_secondary)) {
auto extraattackamt_secondary = std::max({ aabonuses.ExtraAttackChanceSecondary[1], spellbonuses.ExtraAttackChanceSecondary[1], itembonuses.ExtraAttackChanceSecondary[1] });
for (int i = 0; i < extraattackamt_secondary; i++) {
Attack(target, hand, false, false, IsFromSpell);
}
}
}
// you can only triple from the main hand
+122 -9
View File
@@ -860,9 +860,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
case SE_MaxBindWound:
newbon->MaxBindWound += base1;
break;
case SE_ExtraAttackChance:
newbon->ExtraAttackChance += base1;
break;
case SE_SeeInvis:
newbon->SeeInvis = base1;
break;
@@ -984,7 +981,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
case SE_BlockBehind:
newbon->BlockBehind += base1;
break;
case SE_StrikeThrough:
case SE_StrikeThrough2:
newbon->StrikeThrough += base1;
@@ -1177,6 +1173,17 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
break;
}
case SE_Skill_Base_Damage_Mod: {
// Bad data or unsupported new skill
if (base2 > EQ::skills::HIGHEST_SKILL)
break;
if (base2 == ALL_SKILLS)
newbon->DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] += base1;
else
newbon->DamageModifier3[base2] += base1;
break;
}
case SE_SlayUndead: {
if (newbon->SlayUndead[1] < base1)
newbon->SlayUndead[0] = base1; // Rate
@@ -1561,6 +1568,34 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
newbon->Pet_Add_Atk += base1;
break;
case SE_ExtraAttackChance:
{
if (newbon->ExtraAttackChance[0] < base1) {
newbon->ExtraAttackChance[0] = base1;
newbon->ExtraAttackChance[1] = base2 ? base2 : 1;
}
break;
}
case SE_AddExtraAttackPct_1h_Primary:
{
if (newbon->ExtraAttackChancePrimary[0] < base1) {
newbon->ExtraAttackChancePrimary[0] = base1;
newbon->ExtraAttackChancePrimary[1] = base2 ? base2 : 1;
}
break;
}
case SE_AddExtraAttackPct_1h_Secondary:
{
if (newbon->ExtraAttackChanceSecondary[0] < base1) {
newbon->ExtraAttackChanceSecondary[0] = base1;
newbon->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1;
}
break;
}
// to do
case SE_PetDiscipline:
break;
@@ -1715,7 +1750,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
case SE_ChangeFrenzyRad:
{
// redundant to have level check here
if (max != 0 && GetLevel() > max)
break;
if(new_bonus->AggroRange == -1 || effect_value < new_bonus->AggroRange)
{
new_bonus->AggroRange = static_cast<float>(effect_value);
@@ -1725,6 +1762,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
case SE_Harmony:
{
if (max != 0 && GetLevel() > max)
break;
// Harmony effect as buff - kinda tricky
// harmony could stack with a lull spell, which has better aggro range
// take the one with less range in any case
@@ -2323,6 +2362,19 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
break;
}
case SE_Skill_Base_Damage_Mod:
{
// Bad data or unsupported new skill
if (base2 > EQ::skills::HIGHEST_SKILL)
break;
int skill = base2 == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : base2;
if (effect_value < 0 && new_bonus->DamageModifier3[skill] > effect_value)
new_bonus->DamageModifier3[skill] = effect_value;
else if (effect_value > 0 && new_bonus->DamageModifier3[skill] < effect_value)
new_bonus->DamageModifier3[skill] = effect_value;
break;
}
case SE_MinDamageModifier:
{
// Bad data or unsupported new skill
@@ -2358,8 +2410,45 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
}
case SE_ExtraAttackChance:
new_bonus->ExtraAttackChance += effect_value;
{
if (AdditiveWornBonus) {
new_bonus->ExtraAttackChance[0] += effect_value;
new_bonus->ExtraAttackChance[1] = base2 ? base2 : 1;
}
if (new_bonus->ExtraAttackChance[0] < effect_value) {
new_bonus->ExtraAttackChance[0] = effect_value;
new_bonus->ExtraAttackChance[1] = base2 ? base2 : 1;
}
break;
}
case SE_AddExtraAttackPct_1h_Primary:
{
if (AdditiveWornBonus) {
new_bonus->ExtraAttackChancePrimary[0] += effect_value;
new_bonus->ExtraAttackChancePrimary[1] = base2 ? base2 : 1;
}
if (new_bonus->ExtraAttackChancePrimary[0] < effect_value) {
new_bonus->ExtraAttackChancePrimary[0] = effect_value;
new_bonus->ExtraAttackChancePrimary[1] = base2 ? base2 : 1;
}
break;
}
case SE_AddExtraAttackPct_1h_Secondary:
{
if (AdditiveWornBonus) {
new_bonus->ExtraAttackChanceSecondary[0] += effect_value;
new_bonus->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1;
}
if (new_bonus->ExtraAttackChanceSecondary[0] < effect_value) {
new_bonus->ExtraAttackChanceSecondary[0] = effect_value;
new_bonus->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1;
}
break;
}
case SE_PercentXPIncrease:
{
@@ -4252,6 +4341,18 @@ void Mob::NegateSpellsBonuses(uint16 spell_id)
break;
}
case SE_Skill_Base_Damage_Mod:
{
for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++)
{
spellbonuses.DamageModifier3[e] = effect_value;
aabonuses.DamageModifier3[e] = effect_value;
itembonuses.DamageModifier3[e] = effect_value;
}
break;
}
case SE_MinDamageModifier:
{
for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++)
@@ -4276,9 +4377,21 @@ void Mob::NegateSpellsBonuses(uint16 spell_id)
break;
case SE_ExtraAttackChance:
spellbonuses.ExtraAttackChance = effect_value;
aabonuses.ExtraAttackChance = effect_value;
itembonuses.ExtraAttackChance = effect_value;
spellbonuses.ExtraAttackChance[0] = effect_value;
aabonuses.ExtraAttackChance[0] = effect_value;
itembonuses.ExtraAttackChance[0] = effect_value;
break;
case SE_AddExtraAttackPct_1h_Primary:
spellbonuses.ExtraAttackChancePrimary[0] = effect_value;
aabonuses.ExtraAttackChancePrimary[0] = effect_value;
itembonuses.ExtraAttackChancePrimary[0] = effect_value;
break;
case SE_AddExtraAttackPct_1h_Secondary:
spellbonuses.ExtraAttackChanceSecondary[0] = effect_value;
aabonuses.ExtraAttackChanceSecondary[0] = effect_value;
itembonuses.ExtraAttackChanceSecondary[0] = effect_value;
break;
case SE_PercentXPIncrease:
+3 -1
View File
@@ -3272,7 +3272,9 @@ void Bot::AI_Process()
}
TEST_COMBATANTS();
int32 ExtraAttackChanceBonus = (spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance);
auto ExtraAttackChanceBonus =
(spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] +
aabonuses.ExtraAttackChance[0]);
if (ExtraAttackChanceBonus) {
if (p_item && p_item->GetItem()->IsType2HWeapon()) {
+5 -1
View File
@@ -10130,7 +10130,6 @@ void Client::SetAFK(uint8 afk_flag) {
safe_delete(outapp);
}
void Client::SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration) {
uint32 zone_id = ZoneID(zone_short_name);
std::string current_instance_type = str_tolower(instance_type);
@@ -10241,3 +10240,8 @@ void Client::RemoveItem(uint32 item_id, uint32 quantity)
}
}
}
void Client::SetGMStatus(int newStatus) {
if (this->Admin() != newStatus)
database.UpdateGMStatus(this->AccountID(), newStatus);
}
+1
View File
@@ -1523,6 +1523,7 @@ public:
void LoadAccountFlags();
void SetAccountFlag(std::string flag, std::string val);
std::string GetAccountFlag(std::string flag);
void SetGMStatus(int newStatus);
float GetDamageMultiplier(EQ::skills::SkillType how_long_has_this_been_missing);
void Consume(const EQ::ItemData *item, uint8 type, int16 slot, bool auto_consume);
void PlayMP3(const char* fname);
+1 -1
View File
@@ -12997,7 +12997,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
int16 charges = 0;
if (item->Stackable || tmpmer_used)
charges = mp->quantity;
else if ( item->MaxCharges > 1)
else if ( item->MaxCharges >= 1)
charges = item->MaxCharges;
EQ::ItemInstance* inst = database.CreateItem(item, charges);
-2
View File
@@ -75,8 +75,6 @@
#include "mob_movement_manager.h"
#include "npc_scale_manager.h"
#include "../common/content/world_content_service.h"
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "../common/http/httplib.h"
extern QueryServ* QServ;
+4 -1
View File
@@ -445,10 +445,13 @@ struct StatBonuses {
int32 HitChanceEffect[EQ::skills::HIGHEST_SKILL + 2]; //Spell effect Chance to Hit, straight percent increase
int32 DamageModifier[EQ::skills::HIGHEST_SKILL + 2]; //i
int32 DamageModifier2[EQ::skills::HIGHEST_SKILL + 2]; //i
int32 DamageModifier3[EQ::skills::HIGHEST_SKILL + 2]; //i
int32 MinDamageModifier[EQ::skills::HIGHEST_SKILL + 2]; //i
int32 ProcChance; // ProcChance/10 == % increase i = CombatEffects
int32 ProcChanceSPA; // ProcChance from spell effects
int32 ExtraAttackChance;
int32 ExtraAttackChance[2]; // base chance(w/ 2H weapon)=0, amt of extra attacks=1
int32 ExtraAttackChancePrimary[2]; // base chance=0, , amt of extra attacks=1
int32 ExtraAttackChanceSecondary[2]; // base chance=0, , amt of extra attacks=1
int32 DoTShielding;
int32 DivineSaveChance[2]; // Second Chance (base1 = chance, base2 = spell on trigger)
uint32 DeathSave[4]; // Death Pact [0](value = 1 partial 2 = full) [1]=slot [2]=LvLimit [3]=HealAmt
+7 -1
View File
@@ -2158,6 +2158,11 @@ void Lua_Client::RemoveItem(uint32 item_id, uint32 quantity) {
return self->RemoveItem(item_id, quantity);
}
void Lua_Client::SetGMStatus(uint32 newStatus) {
Lua_Safe_Call_Void();
return self->SetGMStatus(newStatus);
}
luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>())
@@ -2525,7 +2530,8 @@ luabind::scope lua_register_client() {
.def("SendToInstance", (void(Lua_Client::*)(std::string,std::string,uint32,float,float,float,float,std::string,uint32))&Lua_Client::SendToInstance)
.def("CountItem", (int(Lua_Client::*)(uint32))&Lua_Client::CountItem)
.def("RemoveItem", (void(Lua_Client::*)(uint32))&Lua_Client::RemoveItem)
.def("RemoveItem", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::RemoveItem);
.def("RemoveItem", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::RemoveItem)
.def("SetGMStatus", (void(Lua_Client::*)(int32))& Lua_Client::SetGMStatus);
}
luabind::scope lua_register_inventory_where() {
+1
View File
@@ -370,6 +370,7 @@ public:
int CountItem(uint32 item_id);
void RemoveItem(uint32 item_id);
void RemoveItem(uint32 item_id, uint32 quantity);
void SetGMStatus(uint32 newStatus);
void SetPrimaryWeaponOrnamentation(uint32 model_id);
void SetSecondaryWeaponOrnamentation(uint32 model_id);
+7 -1
View File
@@ -474,6 +474,11 @@ int Lua_Spell::GetDamageShieldType() {
return self->DamageShieldType;
}
int Lua_Spell::GetRank() {
Lua_Safe_Call_Int();
return self->rank;
}
luabind::scope lua_register_spell() {
return luabind::class_<Lua_Spell>("Spell")
.def(luabind::constructor<>())
@@ -561,7 +566,8 @@ luabind::scope lua_register_spell() {
.def("MaxDist", &Lua_Spell::GetMaxDist)
.def("MaxDistMod", &Lua_Spell::GetMaxDistMod)
.def("MinRange", &Lua_Spell::GetMinRange)
.def("DamageShieldType", &Lua_Spell::GetDamageShieldType);
.def("DamageShieldType", &Lua_Spell::GetDamageShieldType)
.def("Rank", &Lua_Spell::GetRank);
}
#endif
+1
View File
@@ -107,6 +107,7 @@ public:
float GetMaxDistMod();
float GetMinRange();
int GetDamageShieldType();
int GetRank();
};
#endif
+1 -1
View File
@@ -572,7 +572,7 @@ int32 Lua_StatBonuses::GetProcChanceSPA() const {
int32 Lua_StatBonuses::GetExtraAttackChance() const {
Lua_Safe_Call_Int();
return self->ExtraAttackChance;
return self->ExtraAttackChance[0];
}
int32 Lua_StatBonuses::GetDoTShielding() const {
+1 -1
View File
@@ -1624,7 +1624,7 @@ void Merc::AI_Process() {
}
}
int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance;
int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + aabonuses.ExtraAttackChance[0];
if (GetTarget() && ExtraAttackChanceBonus) {
if(zone->random.Roll(ExtraAttackChanceBonus))
+3 -3
View File
@@ -3708,9 +3708,6 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id)
if(!IsValidSpell(spell_id))
return;
if (IsEffectInSpell(spell_id, SE_TwinCastBlocker))
return;
if(IsClient())
{
int32 focus = CastToClient()->GetFocusEffect(focusTwincast, spell_id);
@@ -4862,6 +4859,9 @@ int16 Mob::GetMeleeDamageMod_SE(uint16 skill)
dmg_mod += itembonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] +
itembonuses.DamageModifier2[skill] + spellbonuses.DamageModifier2[skill] + aabonuses.DamageModifier2[skill];
dmg_mod += itembonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] +
itembonuses.DamageModifier3[skill] + spellbonuses.DamageModifier3[skill] + aabonuses.DamageModifier3[skill];
if(dmg_mod < -100)
dmg_mod = -100;
+1
View File
@@ -837,6 +837,7 @@ public:
inline int16 GetSpellPowerDistanceMod() const { return SpellPowerDistanceMod; };
inline void SetSpellPowerDistanceMod(int16 value) { SpellPowerDistanceMod = value; };
int32 GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot = 0);
bool HarmonySpellLevelCheck(int32 spell_id, Mob* target = nullptr);
void CastSpellOnLand(Mob* caster, uint32 spell_id);
void FocusProcLimitProcess();
+16
View File
@@ -3413,6 +3413,21 @@ XS(XS_Client_ReadBook) {
XSRETURN_EMPTY;
}
XS(XS_Client_SetGMStatus); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_SetGMStatus) {
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: Client::SetGMStatus(THIS, int newStatus)"); // @categories Script Utility
{
Client *THIS;
int newStatus = (int)SvIV(ST(1));
VALIDATE_THIS_IS_CLIENT;
THIS->SetGMStatus(newStatus);
THIS->UpdateAdmin(true);
}
XSRETURN_EMPTY;
}
XS(XS_Client_UpdateGroupAAs); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_UpdateGroupAAs) {
dXSARGS;
@@ -5706,6 +5721,7 @@ XS(boot_Client) {
newXSproto(strcpy(buf, "UntrainDisc"), XS_Client_UntrainDisc, file, "$$;$");
newXSproto(strcpy(buf, "UntrainDiscAll"), XS_Client_UntrainDiscAll, file, "$;$");
newXSproto(strcpy(buf, "UpdateAdmin"), XS_Client_UpdateAdmin, file, "$;$");
newXSproto(strcpy(buf, "SetGMStatus"), XS_Client_SetGMStatus, file, "$$");
newXSproto(strcpy(buf, "UpdateGroupAAs"), XS_Client_UpdateGroupAAs, file, "$$$");
newXSproto(strcpy(buf, "UpdateLDoNPoints"), XS_Client_UpdateLDoNPoints, file, "$$$");
newXSproto(strcpy(buf, "UpdateTaskActivity"), XS_Client_UpdateTaskActivity, file, "$$$$;$");
+53 -18
View File
@@ -929,11 +929,31 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills::
if (slot < 0)
return false;
float speed_mod = speed;
float distance_mod = 0.0f;
float distance = other->CalculateDistance(GetX(), GetY(), GetZ());
float hit =
1200.0f + (10 * distance / speed_mod); // Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4)
/*
New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances.
We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer.
Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f
For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though.
*/
if (distance <= 125.0f) {
if (speed != 4.0f) { //Standard functions will always be 4.0f for archery.
distance_mod = (speed - 4.0f) * -20.0f;
distance += distance * distance_mod / 100.0f;
}
}
else if (distance > 125.0f && distance <= 200.0f)
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
else if (distance > 200.0f) {
distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing.
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
}
float hit = 1200.0f + (10 * distance / speed);
ProjectileAtk[slot].increment = 1;
ProjectileAtk[slot].hit_increment = static_cast<uint16>(hit); // This projected hit time if target does NOT MOVE
@@ -951,14 +971,12 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills::
ProjectileAtk[slot].ammo_slot = 0;
ProjectileAtk[slot].skill = skillInUse;
ProjectileAtk[slot].speed_mod = speed_mod;
ProjectileAtk[slot].speed_mod = speed;
SetProjectileAttack(true);
if (item)
SendItemAnimation(other, item, skillInUse, speed);
// else if (IsNPC())
// ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse);
return true;
}
@@ -978,23 +996,40 @@ void Mob::ProjectileAttack()
disable = false;
Mob *target = entity_list.GetMobID(ProjectileAtk[i].target_id);
if (target && target->IsMoving()) { // Only recalculate hit increment if target moving
// Due to frequency that we need to check increment the targets position variables may not be
// updated even if moving. Do a simple check before calculating distance.
if (target && target->IsMoving()) {
/*
Only recalculate hit increment if target is moving.
Due to frequency that we need to check increment the targets position variables may not be
updated even if moving. Do a simple check before calculating distance.
*/
if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()) {
ProjectileAtk[i].tlast_x = target->GetX();
ProjectileAtk[i].tlast_y = target->GetY();
float distance = target->CalculateDistance(
ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z);
float hit = 1200.0f + (10 * distance / ProjectileAtk[i].speed_mod); // Calcuation: 60 =
// Animation Lag, 1.8 =
// Speed modifier for speed
// of (4)
//Recalculate from the original location the projectile was fired in relation to the current targets location.
float distance = target->CalculateDistance(ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z);
float distance_mod = 0.0f;
if (distance <= 125.0f) {
distance_mod = (ProjectileAtk[i].speed_mod - 4.0f) * -20.0f;
distance += distance * distance_mod / 100.0f;
}
else if (distance > 125.0f && distance <= 200.0f)
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
else if (distance > 200.0f) {
distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing.
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
}
float hit = 1200.0f + (10 * distance / ProjectileAtk[i].speed_mod);
ProjectileAtk[i].hit_increment = static_cast<uint16>(hit);
}
}
// We hit I guess?
// Check if we hit.
if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment) {
if (target) {
if (IsNPC()) {
@@ -1496,7 +1531,7 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f
speed = 4.0;
}
if(!angle) {
angle = CalculateHeadingToTarget(to->GetX(), to->GetY()) * 2;
angle = CalculateHeadingToTarget(to->GetX(), to->GetY());
}
if(!tilt) {
tilt = 125;
+60 -15
View File
@@ -3226,6 +3226,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
case SE_Ff_ReuseTimeMax:
case SE_Ff_Value_Min:
case SE_Ff_Value_Max:
case SE_AddExtraAttackPct_1h_Primary:
case SE_AddExtraAttackPct_1h_Secondary:
case SE_Skill_Base_Damage_Mod:
{
break;
}
@@ -4890,7 +4894,7 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id)
break;
case SE_FcTwincast:
if (type == focusTwincast)
if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker))
value = base1;
break;
@@ -5531,7 +5535,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo
break;
case SE_FcTwincast:
if (type == focusTwincast)
if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker))
value = focus_spell.base[i];
break;
@@ -7144,7 +7148,7 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama
return false;
}
bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed) {
/*For mage 'Bolt' line and other various spells.
-This is mostly accurate for how the modern clients handle this effect.
@@ -7169,7 +7173,7 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
//Make sure there is an avialable bolt to be cast.
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
if (ProjectileAtk[i].target_id == 0){
if (ProjectileAtk[i].target_id == 0) {
slot = i;
break;
}
@@ -7178,11 +7182,35 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
if (slot < 0)
return false;
float arc = 0.0f;
float distance_mod = 0.0f;
if (CheckLosFN(spell_target)) {
float speed_mod = speed; //Constant for adjusting speeds to match calculated impact time.
float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ());
float hit = 1200.0f + (10 * distance / speed_mod);
/*
New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances.
We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer.
Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f
For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though.
*/
if (distance <= 125.0f) {
distance_mod = (speed - 4.0f) * -20.0f;
distance += distance * distance_mod / 100.0f;
}
else if (distance > 125.0f && distance <= 200.0f)
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
else if (distance > 200.0f) {
arc = 50.0f - ((distance - 200.0f) * 0.266f); //Arc angle gets drastically larger if >200 distance, lets lower it down gradually for better effect.
arc = std::max(arc, 20.0f); //No lower than 20 arc
distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing.
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
}
float hit = 1200.0f + (10 * distance / speed);
ProjectileAtk[slot].increment = 1;
ProjectileAtk[slot].hit_increment = static_cast<uint16>(hit); //This projected hit time if target does NOT MOVE
@@ -7192,34 +7220,33 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
ProjectileAtk[slot].origin_y = GetY();
ProjectileAtk[slot].origin_z = GetZ();
ProjectileAtk[slot].skill = EQ::skills::SkillConjuration;
ProjectileAtk[slot].speed_mod = speed_mod;
ProjectileAtk[slot].speed_mod = speed;
SetProjectileAttack(true);
}
//This will use the correct graphic as defined in the player_1 field of spells_new table. Found in UF+ spell files.
if (RuleB(Spells, UseLiveSpellProjectileGFX)) {
ProjectileAnimation(spell_target,0, false, speed,0,0,0, spells[spell_id].player_1);
ProjectileAnimation(spell_target, 0, false, speed, 0.0f, 0.0f, arc, spells[spell_id].player_1);
}
//This allows limited support for server using older spell files that do not contain data for bolt graphics.
else {
//Only use fire graphic for fire spells.
if (spells[spell_id].resisttype == RESIST_FIRE) {
if (IsClient()){
if (IsClient()) {
if (CastToClient()->ClientVersionBit() <= 4) //Titanium needs alternate graphic.
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, speed);
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_Titanium)), false, speed, 0.0f, 0.0f, arc);
else
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, speed);
}
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_SOF)), false, speed, 0.0f, 0.0f, arc);
}
else
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, speed);
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_NPC)), false, speed, 0.0f, 0.0f, arc);
}
//Default to an arrow if not using a mage bolt (Use up to date spell file and enable above rules for best results)
else
ProjectileAnimation(spell_target,0, 1, speed);
ProjectileAnimation(spell_target, 0, 1, speed, 0.0f, 0.0f, arc);
}
if (spells[spell_id].CastingAnim == 64)
@@ -7564,3 +7591,21 @@ void Client::BreakFeignDeathWhenCastOn(bool IsResisted)
MessageString(Chat::SpellFailure,FD_CAST_ON);
}
}
bool Mob::HarmonySpellLevelCheck(int32 spell_id, Mob *target)
{
//'this' = caster of spell
if (!target) {
return false;
}
for (int i = 0; i < EFFECT_COUNT; i++) {
// not important to check limit on SE_Lull as it doesnt have one and if the other components won't land, then SE_Lull wont either
if (spells[spell_id].effectid[i] == SE_ChangeFrenzyRad || spells[spell_id].effectid[i] == SE_Harmony) {
if ((spells[spell_id].max[i] != 0 && target->GetLevel() > spells[spell_id].max[i]) || target->GetSpecialAbility(IMMUNE_PACIFY)) {
return false;
}
}
}
return true;
}
+9 -11
View File
@@ -224,17 +224,9 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(IsClient() && GetTarget() && IsHarmonySpell(spell_id))
{
for(int i = 0; i < EFFECT_COUNT; i++) {
// not important to check limit on SE_Lull as it doesnt have one and if the other components won't land, then SE_Lull wont either
if (spells[spell_id].effectid[i] == SE_ChangeFrenzyRad || spells[spell_id].effectid[i] == SE_Harmony) {
if((spells[spell_id].max[i] != 0 && GetTarget()->GetLevel() > spells[spell_id].max[i]) || GetTarget()->GetSpecialAbility(IMMUNE_PACIFY)) {
InterruptSpell(CANNOT_AFFECT_NPC, 0x121, spell_id);
return(false);
}
}
}
if (IsClient() && IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, entity_list.GetMobID(target_id))) {
InterruptSpell(SPELL_NO_EFFECT, 0x121, spell_id);
return false;
}
if (HasActiveSong() && IsBardSong(spell_id)) {
@@ -3789,6 +3781,12 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r
return false;
}
}
//Need this to account for special AOE cases.
if (IsClient() && IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, spelltar)) {
MessageString(Chat::SpellFailure, SPELL_NO_EFFECT);
return false;
}
// Block next spell effect should be used up first(since its blocking the next spell)
if(CanBlockSpell()) {
int buff_count = GetMaxTotalSlots();
+18
View File
@@ -4977,3 +4977,21 @@ void ZoneDatabase::SetEXPModifier(uint32 character_id, uint32 zone_id, double ex
);
database.QueryDatabase(query);
}
void ZoneDatabase::UpdateGMStatus(uint32 accID, int newStatus)
{
if (accID) {
std::string query = fmt::format(
SQL(
UPDATE
`account`
SET `status` = {}
WHERE
`id` = {}
),
newStatus,
accID
);
database.QueryDatabase(query);
}
}
+3
View File
@@ -289,6 +289,9 @@ public:
void DeleteBuyLines(uint32 CharID);
void UpdateBuyLine(uint32 CharID, uint32 BuySlot, uint32 Quantity);
void UpdateGMStatus(uint32 accID, int newStatus);
/**
************************************************
* Character