mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-17 03:08:26 +00:00
Merge remote-tracking branch 'upstream/master' into spaupdate03
This commit is contained in:
+1
-1
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -451,6 +451,7 @@ SET(repositories
|
||||
)
|
||||
|
||||
SET(common_headers
|
||||
additive_lagged_fibonacci_engine.h
|
||||
any.h
|
||||
base_packet.h
|
||||
base_data.h
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -107,6 +107,7 @@ public:
|
||||
float GetMaxDistMod();
|
||||
float GetMinRange();
|
||||
int GetDamageShieldType();
|
||||
int GetRank();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +289,9 @@ public:
|
||||
void DeleteBuyLines(uint32 CharID);
|
||||
void UpdateBuyLine(uint32 CharID, uint32 BuySlot, uint32 Quantity);
|
||||
|
||||
|
||||
void UpdateGMStatus(uint32 accID, int newStatus);
|
||||
|
||||
/**
|
||||
************************************************
|
||||
* Character
|
||||
|
||||
Reference in New Issue
Block a user