Merge branch 'master' into web_interface

This commit is contained in:
KimLS 2014-08-11 13:51:11 -07:00
commit 119e0bb0ac
22 changed files with 1213 additions and 47 deletions

16
.travis.yml Normal file
View File

@ -0,0 +1,16 @@
language: cpp
compiler: gcc
before_install:
- sudo apt-get update -qq
- sudo apt-get install -y libmysqlclient-dev libperl-dev libboost-dev liblua5.1-0-dev zlib1g-dev
script:
- cmake -G "Unix Makefiles" -DEQEMU_BUILD_TESTS=ON -DEQEMU_ENABLE_BOTS=ON
- make
- ./bin/tests
branches:
only: master
notifications:
email: false
irc:
channels: "irc.eqemulator.net#eqemucoders"
os: linux

View File

@ -1,5 +1,53 @@
EQEmu - Custom Game Implementation for EverQuest
EQEmu
===
Dependencies can be obtained at http://eqemu.github.io
[![Build Status](https://travis-ci.org/EQEmu/Server.svg?branch=master)](https://travis-ci.org/EQEmu/Server)
Overview
---
EQEmu is a custom server implementation for EverQuest
Dependencies
---
For Windows: http://eqemu.github.io
Login Server dependencies for Windows/Linux/OSX: http://eqemu.github.io
For Debian based distros (adjust to your local flavor):
- libmysqlclient-dev
- libperl-dev
- liblua5.1-0-dev (5.2 should work as well)
- libboost-dev
Further instructions on building the source can be found on the
[wiki](http://wiki.eqemulator.org/i?M=Wiki).
Bug reports
---
Please use the [issue tracker](issue-tracker) provided by GitHub to send us bug
reports or feature requests.
The [EQEmu Forums](http://www.eqemulator.org/forums/) also have forums to submit
bugs/get help with bugs.
Contributions
---
The preferred way to contribute is to fork the repo and submit a pull request on
GitHub. If you need help with your changes, you can always post on the forums or
try IRC. You can also post unified diffs (`git diff` should do the trick) on the
[Server Code Submissions](http://www.eqemulator.org/forums/forumdisplay.php?f=669)
forum, although pull requests will be much quicker and easier on all parties.
Contact
---
- **User IRC Channel**: `#eqemu` on `irc.eqemulator.net`
- **Developer IRC Channel**: `#eqemucoders` on `irc.eqemulator.net`
- [EQEmulator Forums](http://www.eqemulator.org/forums)
- [EQEmulator Wiki](http://wiki.eqemulator.org/i?M=Wiki)
More Information: https://github.com/EQEmu/Server/wiki

View File

@ -1,5 +1,17 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
== 08/02/2014 ==
Kayen: Implemented spell_news fields
- npc_no_los (check if LOS is required for spells)
- InCombat, OutofCombat - Used together to restrict spells to only be cast while
in/out of combat (beneficial) or if target is in/out of combat (detrimental).
-min_dist, min_dist_mod, max_dist, max_dist_mod - Scales spell power based on targets distance from caster.
*This will require further work to fully implement but will work with 90% of live spells as is.
*If making custom spells do not include effects that can't be scaled (like a spell trigger)
- min_rage sets minimum distance range that must be away from target.
Required SQL: utils/sql/git/required/2014_08_02_spells_new.sql
== 07/31/2014 ==
Uleat: More inventory slot constant conversions. This should be the bulk of everything..but, due to the size of the server code, there
may be some hidden ones. (client_packet.cpp and the client translators still need a thorough review.)

View File

@ -70,7 +70,7 @@ void ExportSpells(SharedDatabase *db) {
}
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = "SELECT * FROM spells_new ORDER BY id";
const char *query = "SELECT * FROM spells_new ORDER BY id";
MYSQL_RES *result;
MYSQL_ROW row;
if(db->RunQuery(query, strlen(query), errbuf, &result)) {
@ -176,7 +176,7 @@ void ExportBaseData(SharedDatabase *db) {
}
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = "SELECT * FROM base_data ORDER BY level, class";
const char *query = "SELECT * FROM base_data ORDER BY level, class";
MYSQL_RES *result;
MYSQL_ROW row;
if(db->RunQuery(query, strlen(query), errbuf, &result)) {

View File

@ -61,7 +61,7 @@ int main(int argc, char **argv) {
int GetSpellColumns(SharedDatabase *db) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = "DESCRIBE spells_new";
const char *query = "DESCRIBE spells_new";
MYSQL_RES *result;
MYSQL_ROW row;
int res = 0;
@ -234,4 +234,4 @@ void ImportBaseData(SharedDatabase *db) {
}
fclose(f);
}
}

View File

@ -1710,7 +1710,7 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) {
for (y = 0; y < 16; y++)
sp[tempid].deities[y]=atoi(row[126+y]);
sp[tempid].uninterruptable=atoi(row[146]);
sp[tempid].uninterruptable=atoi(row[146]) != 0;
sp[tempid].ResistDiff=atoi(row[147]);
sp[tempid].dot_stacking_exempt=atoi(row[148]);
sp[tempid].RecourseLink = atoi(row[150]);
@ -1726,6 +1726,7 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) {
sp[tempid].EndurCost=atoi(row[166]);
sp[tempid].EndurTimerIndex=atoi(row[167]);
sp[tempid].IsDisciplineBuff = atoi(row[168]) != 0;
sp[tempid].HateAdded=atoi(row[173]);
sp[tempid].EndurUpkeep=atoi(row[174]);
sp[tempid].numhitstype = atoi(row[175]);
@ -1741,19 +1742,26 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) {
sp[tempid].viral_targets = atoi(row[191]);
sp[tempid].viral_timer = atoi(row[192]);
sp[tempid].NimbusEffect = atoi(row[193]);
sp[tempid].directional_start = (float)atoi(row[194]);
sp[tempid].directional_end = (float)atoi(row[195]);
sp[tempid].directional_start = static_cast<float>(atoi(row[194]));
sp[tempid].directional_end = static_cast<float>(atoi(row[195]));
sp[tempid].not_extendable = atoi(row[197]) != 0;
sp[tempid].suspendable = atoi(row[200]) != 0;
sp[tempid].viral_range = atoi(row[201]);
sp[tempid].spellgroup=atoi(row[207]);
sp[tempid].rank = atoi(row[208]);
sp[tempid].powerful_flag=atoi(row[209]);
sp[tempid].CastRestriction = atoi(row[211]);
sp[tempid].AllowRest = atoi(row[212]) != 0;
sp[tempid].NotOutofCombat = atoi(row[213]) != 0;
sp[tempid].NotInCombat = atoi(row[214]) != 0;
sp[tempid].InCombat = atoi(row[213]) != 0;
sp[tempid].OutofCombat = atoi(row[214]) != 0;
sp[tempid].aemaxtargets = atoi(row[218]);
sp[tempid].maxtargets = atoi(row[219]);
sp[tempid].persistdeath = atoi(row[224]) != 0;
sp[tempid].min_dist = atof(row[227]);
sp[tempid].min_dist_mod = atof(row[228]);
sp[tempid].max_dist = atof(row[229]);
sp[tempid].max_dist_mod = atof(row[230]);
sp[tempid].min_range = static_cast<float>(atoi(row[231]));
sp[tempid].DamageShieldType = 0;
}
mysql_free_result(result);
@ -1767,7 +1775,7 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) {
int SharedDatabase::GetMaxBaseDataLevel() {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = "SELECT MAX(level) FROM base_data";
const char *query = "SELECT MAX(level) FROM base_data";
MYSQL_RES *result;
MYSQL_ROW row;
int32 ret = 0;
@ -1818,7 +1826,7 @@ bool SharedDatabase::LoadBaseData() {
void SharedDatabase::LoadBaseData(void *data, int max_level) {
char *base_ptr = reinterpret_cast<char*>(data);
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = "SELECT * FROM base_data ORDER BY level, class ASC";
const char *query = "SELECT * FROM base_data ORDER BY level, class ASC";
MYSQL_RES *result;
MYSQL_ROW row;

View File

@ -1040,6 +1040,15 @@ bool IsCastonFadeDurationSpell(uint16 spell_id)
return false;
}
bool IsPowerDistModSpell(uint16 spell_id)
{
if (IsValidSpell(spell_id) &&
(spells[spell_id].max_dist_mod || spells[spell_id].min_dist_mod) && spells[spell_id].max_dist > spells[spell_id].min_dist)
return true;
return false;
}
uint32 GetPartialMeleeRuneReduction(uint32 spell_id)
{
for (int i = 0; i < EFFECT_COUNT; ++i)

View File

@ -131,10 +131,10 @@ typedef enum {
/* 41 */ ST_Group = 0x29,
/* 42 */ ST_Directional = 0x2a, //ae around this target between two angles
/* 43 */ ST_GroupClientAndPet = 0x2b,
/* 44 */ ST_Beam = 0x2c, //like directional but facing in front of you always
/* 44 */ //ST_Beam = 0x2c, //like directional but facing in front of you always
/* 45 */ //ST_Ring = 0x2d, // Like a mix of PB ae + rain spell(has ae duration)
/* 46 */ ST_TargetsTarget = 0x2e, // uses the target of your target
/* 47 */ //ST_PetMaster = 0x2e, // uses the master as target
/* 47 */ ST_PetMaster = 0x2f, // uses the master as target
} SpellTargetType;
typedef enum {
@ -697,8 +697,8 @@ struct SPDat_Spell_Struct
/* 164 */ // for most spells this appears to mimic ResistDiff
/* 166 */ int EndurCost;
/* 167 */ int8 EndurTimerIndex;
/* 168 */ //int IsDisciplineBuff; //Will goto the combat window when cast
/* 169 */
/* 168 */ bool IsDisciplineBuff; //Will goto the combat window when cast
/* 169 - 172*/ //These are zero for ALL spells
/* 173 */ int HateAdded;
/* 174 */ int EndurUpkeep;
/* 175 */ int numhitstype; // defines which type of behavior will tick down the numhit counter.
@ -721,23 +721,30 @@ struct SPDat_Spell_Struct
/* 197 */ bool not_extendable;
/* 198- 199 */
/* 200 */ bool suspendable; // buff is suspended in suspended buff zones
/* 201 - 202 */
/* 201 */ int viral_range;
/* 202 */
/* 203 */ //int songcap; // individual song cap (how live currently does it, not implemented)
/* 204 - 206 */
/* 207 */ int spellgroup;
/* 208 */ // int rank - increments AA effects with same name
/* 208 */ int rank; //increments AA effects with same name
/* 209 */ int powerful_flag; // Need more investigation to figure out what to call this, for now we know -1 makes charm spells not break before their duration is complete, it does alot more though
/* 210 */ // bool DurationFrozen; ???
/* 211 */ int CastRestriction; //Various restriction categories for spells most seem targetable race related but have also seen others for instance only castable if target hp 20% or lower or only if target out of combat
/* 212 */ bool AllowRest;
/* 213 */ bool NotOutofCombat; //Fail if cast out of combat
/* 214 */ bool NotInCombat; //Fail if cast in combat
/* 213 */ bool InCombat; //Allow spell if target is in combat
/* 214 */ bool OutofCombat; //Allow spell if target is out of combat
/* 215 - 217 */
/* 219 */ int aemaxtargets; //
/* 219 */ int maxtargets; // is used for beam and ring spells for target # limits (not implemented)
/* 220 - 223 */
/* 218 */ int aemaxtargets; //Is used for various AE effects
/* 219 */ int maxtargets; //Is used for beam and ring spells for target # limits (not implemented)
/* 220 - 223 */
/* 224 */ bool persistdeath; // buff doesn't get stripped on death
/* 225 - 236 */ // Not in DB
/* 225 - 226 */
/* 227 */ float min_dist; //spell power modified by distance from caster (Min Distance)
/* 228 */ float min_dist_mod; //spell power modified by distance from caster (Modifier at Min Distance)
/* 229 */ float max_dist; //spell power modified by distance from caster (Max Distance)
/* 230 */ float max_dist_mod; //spell power modified by distance from caster (Modifier at Max Distance)
/* 231 */ float min_range; //Min casting range
/* 232 - 236 */
uint8 DamageShieldType; // This field does not exist in spells_us.txt
};
@ -838,6 +845,7 @@ bool IsPersistDeathSpell(uint16 spell_id);
bool IsSuspendableSpell(uint16 spell_id);
uint32 GetMorphTrigger(uint32 spell_id);
bool IsCastonFadeDurationSpell(uint16 spell_id);
bool IsPowerDistModSpell(uint16 spell_id);
uint32 GetPartialMeleeRuneReduction(uint32 spell_id);
uint32 GetPartialMagicRuneReduction(uint32 spell_id);
uint32 GetPartialMeleeRuneAmount(uint32 spell_id);

1
utils/scripts/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
opcode_handlers_output

View File

@ -0,0 +1,920 @@
#! /usr/bin/env python
#
# This script generates cross-references to show associated (handled) opcodes
# between the server and client. It will generate files for each client and
# server found, and provide some basic information..such as opcode names and
# values, server handler and whether opcodes are translated on tx/rx, etc...
#
# It's currently limited to the 'Zone' server..but, can be expounded upon to
# include other servers and clients, and other criteria and features.
import sys
import os
from time import time, ctime
DEBUG = 1 # {0 - normal, 1 - verbose, 2 - in-depth}
base_path = os.getcwd()[:-14] # '/utils/scripts'
client_list = ['6.2', 'Titanium', 'SoF', 'SoD', 'Underfoot', 'RoF', 'RoF2', 'ClientTest']
server_list = ['Login', 'World', 'Zone', 'UCS', 'ServerTest']
client_opcodes = {}
server_opcodes = {}
client_encodes = {}
client_decodes = {}
server_handlers = {}
out_files = {}
def main():
""" Call each method independently and track success """
fault = False
faults = []
print('')
if fault is False:
fault = not createoutputdirectory()
if fault is True:
faults.append('createoutputdirectory()')
if fault is False:
fault = not opendebugfile()
if fault is True:
faults.append('opendebugfile()')
if fault is False:
print('Loading source data...')
if fault is False:
fault = not loadclientopcodes()
if fault is True:
faults.append('loadclientopcodes()')
if fault is False:
fault = not loadserveropcodes()
if fault is True:
faults.append('loadserveropcodes()')
if fault is False:
fault = not loadclienttranslators()
if fault is True:
faults.append('loadclienttranslators()')
if fault is False:
fault = not loadserverhandlers()
if fault is True:
faults.append('loadserverhandlers()')
if fault is False:
fault = not discoverserverhandlers()
if fault is True:
faults.append('discoverserverhandlers()')
if fault is False:
fault = not clearemptyserverentries()
if fault is True:
faults.append('clearemptyserverentries()')
if fault is False:
print('Creating output streams...')
if fault is False:
fault = not openoutputfiles()
if fault is True:
faults.append('openoutputfiles()')
if fault is False:
print('Parsing opcode data...')
if fault is False:
fault = not parseclientopcodedata()
if fault is True:
faults.append('parseclientopcodedata()')
if fault is False:
fault = not parseserveropcodedata()
if fault is True:
faults.append('parseserveropcodedata()')
if fault is False:
print('Destroying output streams...')
# these should always be processed..verbose or silent
if not closeoutputfiles():
faults.append('closeoutputfiles()')
if not closedebugfile():
faults.append('closedebugfile()')
if len(faults) > 0:
message = 'Script failed due to errors in:\n'
for entry in faults:
message += ' {0}'.format(entry)
print(message)
return
def createoutputdirectory():
""" Check for output directory - create if does not exist """
try:
output_path = '{0}/utils/scripts/opcode_handlers_output'.format(base_path)
if DEBUG >= 1:
print(output_path)
if not os.path.exists(output_path):
os.mkdir(output_path)
return True
except:
if DEBUG >= 2:
print('EXCEPTION ERROR->createoutputdirectory({0})'.format(sys.exc_info()[0]))
return False
def opendebugfile():
file_name = '{0}/utils/scripts/opcode_handlers_output/DEBUG.txt'.format(base_path)
if DEBUG >= 1:
print(file_name)
out_files['DEBUG'] = open(file_name, 'w')
return True
def loadclientopcodes():
bad_clients = []
for client in client_list:
try:
short_name = '{0}{1}{2}'.format(
'/patch_',
client,
'.conf')
file_name = '{0}{1}{2}'.format(
base_path,
'/utils/patches',
short_name)
if DEBUG >= 1:
print(file_name)
with open(file_name, 'r') as data_file:
client_opcodes[client] = {} # force empty dictionary to avoid collisions
for data_line in data_file:
key_begin = data_line.find('OP_')
key_end = data_line.find('=', key_begin)
if not key_begin == 0 or key_end < 0:
continue
val_begin = data_line.find('0x', key_end)
val_end = val_begin + 6 # max size is always 6 bytes
if val_begin < 0:
continue
value = int(data_line[(val_begin + 2):val_end].lower(), 16)
if value == 0:
continue
client_opcodes[client][data_line[key_begin:key_end]] = '0x{0}'.format(hex(value)[2:].zfill(4))
if DEBUG >= 2:
print('[{0}][{1}] = {2} (int: {3})'.format(
client,
data_line[key_begin:key_end],
client_opcodes[client][data_line[key_begin:key_end]],
value))
data_file.close()
except:
if DEBUG >= 2:
print('EXCEPTION ERROR->loadclientopcodes({0})'.format(sys.exc_info()[0]))
bad_clients.append(client)
for bad_client in bad_clients:
if DEBUG >= 1:
print('Deleting \'{0}\' client from search criteria...'.format(bad_client))
client_list.remove(bad_client)
if DEBUG >= 1:
print('Deleting stale entries for \'{0}\' client...'.format(bad_client))
if bad_client in client_opcodes:
del client_opcodes[bad_client]
if not len(client_list) > 0:
return False
return True
def loadserveropcodes():
try:
value = 0
server_opcodes['OP_Unknown'] = value
value += 1
if DEBUG >= 2:
print('N[Server](OP_Unknown) = {0}'.format(server_opcodes['OP_Unknown']))
file_name = '{0}{1}'.format(
base_path,
'/common/emu_oplist.h')
if DEBUG >= 1:
print(file_name)
with open(file_name, 'r') as data_file:
for data_line in data_file:
val_begin = data_line.find('OP_', 2)
val_end = data_line.find(')', val_begin)
if val_begin < 0 or val_end < 0:
continue
if data_line[:1] == 'N':
server_opcodes[data_line[val_begin:val_end]] = value
value += 1
if DEBUG >= 2:
print('N[{0}]({1}) = {2}'.format(
'Server',
data_line[val_begin:val_end],
server_opcodes[data_line[val_begin:val_end]]))
data_file.close()
file_name = '{0}{1}'.format(
base_path,
'/common/mail_oplist.h')
if DEBUG >= 1:
print(file_name)
with open(file_name, 'r') as data_file:
for data_line in data_file:
val_begin = data_line.find('OP_', 2)
val_end = data_line.find(')', val_begin)
if val_begin < 0 or val_end < 0:
continue
if data_line[:1] == 'N':
server_opcodes[data_line[val_begin:val_end]] = value
value += 1
if DEBUG >= 2:
print('N[{0}]({1}) = {2}'.format(
'Server',
data_line[val_begin:val_end],
server_opcodes[data_line[val_begin:val_end]]))
data_file.close()
except:
if DEBUG >= 2:
print('EXCEPTION ERROR->loadserveropcodes({0})'.format(sys.exc_info()[0]))
return False
return True
def loadclienttranslators():
for client in client_list:
if client == '6.2':
short_name = '{0}'.format('/Client62_ops.h')
else:
short_name = '{0}{1}{2}'.format(
'/',
client,
'_ops.h')
try:
file_name = '{0}{1}{2}'.format(
base_path,
'/common/patches',
short_name)
if DEBUG >= 1:
print(file_name)
with open(file_name, 'r') as data_file:
client_encodes[client] = []
client_decodes[client] = []
for data_line in data_file:
val_begin = data_line.find('OP_', 2)
val_end = data_line.find(')', val_begin)
if val_begin < 0 or val_end < 0:
continue
if data_line[:1] == 'E':
client_encodes[client].append(data_line[val_begin:val_end])
if DEBUG >= 2:
print('E[{0}]({1}) (listed: {2})'.format(
client,
data_line[val_begin:val_end],
data_line[val_begin:val_end] in client_encodes[client]))
elif data_line[:1] == 'D':
client_decodes[client].append(data_line[val_begin:val_end])
if DEBUG >= 2:
print('D[{0}]({1}) (listed: {2})'.format(
client,
data_line[val_begin:val_end],
data_line[val_begin:val_end] in client_decodes[client]))
data_file.close()
except:
if DEBUG >= 2:
print('EXCEPTION ERROR->loadclienttranslators({0})'.format(sys.exc_info()[0]))
return False
# there's always going to be at least one client with one encode or decode
if not len(client_encodes) > 0 and not len(client_decodes) > 0:
return False
return True
def loadserverhandlers():
""" Load pre-designated SERVER opcode handlers """
# TODO: handle remarked out definitions in file (i.e., // and /**/);
bad_servers = []
for server in server_list:
try:
if server == 'Login':
if DEBUG >= 1:
print('No pre-designated server opcode handlers for \'{0}\''.format(server))
continue
elif server == 'World':
if DEBUG >= 1:
print('No pre-designated server opcode handlers for \'{0}\''.format(server))
continue
elif server == 'Zone':
file_name = '{0}{1}'.format(
base_path,
'/zone/client_packet.cpp')
if DEBUG >= 1:
print(file_name)
with open(file_name, 'r') as data_file:
server_handlers[server] = {}
can_run = False
line_no = 0
for data_line in data_file:
line_no += 1
if can_run is False:
if data_line[:19] == 'void MapOpcodes() {':
can_run = True
continue
if data_line[0:1] == '}':
break
key_begin = data_line.find('OP_')
key_end = data_line.find(']', key_begin)
if key_begin < 0 or key_end < 0:
continue
val_begin = data_line.find('Client::', key_end)
val_end = data_line.find(';', val_begin)
if val_begin < 0 or val_end < 0:
continue
# TODO: add continue on 'in server_opcodes' failure
if not data_line[key_begin:key_end] in server_handlers[server]:
server_handlers[server][data_line[key_begin:key_end]] = []
server_handlers[server][data_line[key_begin:key_end]].append(
'../zone/client_packet.cpp({0}:{1}) \'{2}\''.format(
line_no,
key_begin,
data_line[val_begin:val_end]))
if DEBUG >= 2:
print('[{0}][{1}]({2}) [{3}]'.format(
server,
data_line[key_begin:key_end],
data_line[val_begin:val_end],
data_line[val_begin:val_end] in server_handlers[server][data_line[key_begin:key_end]]))
data_file.close()
elif server == 'UCS':
if DEBUG >= 1:
print('No pre-designated server opcode handlers for \'{0}\''.format(server))
continue
else:
if DEBUG >= 1:
print('No pre-designated server opcode handlers for \'{0}\''.format(server))
if DEBUG >= 2:
print('->LoadServerHandlers(Someone added a new server and forgot to code for the data load...)')
continue
except:
if DEBUG >= 2:
print('EXCEPTION ERROR->loadserverhandlers({0})'.format(sys.exc_info()[0]))
bad_servers.append(server)
for bad_server in bad_servers:
if DEBUG >= 1:
print('Deleting \'{0}\' server from search criteria...'.format(bad_server))
server_list.remove(bad_server)
if DEBUG >= 1:
print('Deleting stale entries for \'{0}\' server...'.format(bad_server))
if bad_server in server_handlers:
del server_handlers[bad_server]
if not len(server_list) > 0:
return False
return True
def discoverserverhandlers():
""" Load undefined SERVER opcode handlers using 'discovery' method """
locations = {}
for server in server_list: # initialize lists for any remaining servers
locations[server] = []
# manually enter search locations
#if 'Server' in locations:
# locations['Server'].append('/<local_path>/<file_name>.<ext>')
# TODO: if/how to include perl/lua handlers...
if 'Login' in locations:
locations['Login'].append('/loginserver/Client.cpp')
locations['Login'].append('/loginserver/ServerManager.cpp')
locations['Login'].append('/loginserver/WorldServer.cpp')
if 'World' in locations:
locations['World'].append('/world/client.cpp')
# the bulk of opcodes are handled in 'Zone' - if processing occurs on a different
# server, you will need to manually trace 'ServerPacket' to the deferred location
if 'Zone' in locations:
locations['Zone'].append('/zone/AA.cpp')
locations['Zone'].append('/zone/attack.cpp')
locations['Zone'].append('/zone/bot.cpp')
locations['Zone'].append('/zone/client.cpp')
locations['Zone'].append('/zone/client_packet.cpp')
locations['Zone'].append('/zone/client_process.cpp')
locations['Zone'].append('/zone/command.cpp')
locations['Zone'].append('/zone/corpse.cpp')
locations['Zone'].append('/zone/doors.cpp')
locations['Zone'].append('/zone/effects.cpp')
locations['Zone'].append('/zone/entity.cpp')
locations['Zone'].append('/zone/exp.cpp')
locations['Zone'].append('/zone/groups.cpp')
locations['Zone'].append('/zone/guild.cpp')
locations['Zone'].append('/zone/guild_mgr.cpp')
locations['Zone'].append('/zone/horse.cpp')
locations['Zone'].append('/zone/inventory.cpp')
locations['Zone'].append('/zone/loottables.cpp')
locations['Zone'].append('/zone/merc.cpp')
locations['Zone'].append('/zone/mob.cpp')
locations['Zone'].append('/zone/MobAI.cpp')
locations['Zone'].append('/zone/Object.cpp')
locations['Zone'].append('/zone/pathing.cpp')
locations['Zone'].append('/zone/petitions.cpp')
locations['Zone'].append('/zone/questmgr.cpp')
locations['Zone'].append('/zone/raids.cpp')
locations['Zone'].append('/zone/special_attacks.cpp')
locations['Zone'].append('/zone/spells.cpp')
locations['Zone'].append('/zone/spell_effects.cpp')
locations['Zone'].append('/zone/tasks.cpp')
locations['Zone'].append('/zone/titles.cpp')
locations['Zone'].append('/zone/tradeskills.cpp')
locations['Zone'].append('/zone/trading.cpp')
locations['Zone'].append('/zone/trap.cpp')
locations['Zone'].append('/zone/tribute.cpp')
locations['Zone'].append('/zone/worldserver.cpp')
locations['Zone'].append('/zone/zone.cpp')
locations['Zone'].append('/zone/zonedb.cpp')
locations['Zone'].append('/zone/zoning.cpp')
if 'UCS' in locations:
locations['UCS'].append('/ucs/clientlist.cpp')
locations['UCS'].append('/ucs/database.cpp')
for server in server_list:
if not server in server_handlers:
server_handlers[server] = {}
for location in locations[server]:
try:
file_name = '{0}{1}'.format(
base_path,
location)
if DEBUG >= 1:
print(file_name)
with open(file_name, 'r') as data_file:
line_no = 0
hint = 'Near beginning of file'
for data_line in data_file:
line_no += 1
if data_line[:1].isalpha():
hint_end = data_line.find('(')
if not hint_end < 0:
hint_begin = hint_end - 1
while not hint_begin < 0:
if data_line[(hint_begin - 1):hint_begin].isspace():
if not data_line[hint_begin:(hint_begin + 1)].isalpha():
hint_begin += 1
hint = 'Near {0}'.format(data_line[hint_begin:hint_end])
break
hint_begin -= 1
op_begin = data_line.find('OP_')
if op_begin < 0:
continue
if data_line[(op_begin - 20):op_begin] == 'EQApplicationPacket(':
key_begin = op_begin
key_end = data_line.find(',', key_begin)
elif data_line[(op_begin - 12):op_begin] == '->SetOpcode(':
key_begin = op_begin
key_end = data_line.find(')', key_begin)
elif data_line[(op_begin - 5):op_begin] == 'case ':
key_begin = op_begin
key_end = data_line.find(':', key_begin)
else:
continue
if key_end < 0:
continue
if not data_line[key_begin:key_end] in server_opcodes:
out_files['DEBUG'].write('Illegal Opcode Found: ..{0} ({1}:{2}) \'{3}\'\n'.format(
location,
line_no,
key_begin,
data_line[key_begin:key_end]
))
continue
if not data_line[key_begin:key_end] in server_handlers[server]:
server_handlers[server][data_line[key_begin:key_end]] = []
if not data_line in server_handlers[server][data_line[key_begin:key_end]]:
server_handlers[server][data_line[key_begin:key_end]].append(
'..{0}({1}:{2}) \'{3}\''.format(
location,
line_no,
key_begin,
hint))
except:
if DEBUG >= 2:
print('EXCEPTION ERROR->discoverserverhandlers({0})'.format(sys.exc_info()[0]))
return True
def clearemptyserverentries():
bad_servers = []
for server in server_list:
if len(server_handlers[server]) == 0:
bad_servers.append(server)
else:
bad_opcodes = []
for opcode in server_handlers[server]:
if len(server_handlers[server][opcode]) == 0:
bad_opcodes.append(opcodes)
for bad_opcode in bad_opcodes:
del server_handlers[server][bad_opcode]
if len(server_handlers[server]) == 0:
bad_servers.append(server)
for bad_server in bad_servers:
if DEBUG >= 1:
print('Deleting \'{0}\' server from search criteria...'.format(bad_server))
print('Deleting stale entries for \'{0}\' server...'.format(bad_server))
del server_handlers[bad_server]
server_list.remove(bad_server)
return True
def openoutputfiles():
""" Open output files in 'w' mode - create/overwrite mode """
try:
file_name = '{0}/utils/scripts/opcode_handlers_output/Report.txt'.format(base_path)
if DEBUG >= 1:
print(file_name)
out_files['Report'] = open(file_name, 'w')
for client in client_list:
file_name = '{0}{1}{2}{3}'.format(
base_path,
'/utils/scripts/opcode_handlers_output/',
client,
'_opcode_handlers.txt')
if DEBUG >= 1:
print(file_name)
out_files[client] = open(file_name, 'w')
message = \
'>> \'Opcode-Handler\' analysis for \'{0}\' client\n' \
'>> file generated @ {1}\n' \
'\n'.format(
client,
ctime(time()))
out_files[client].write(message)
if DEBUG >= 2:
print(message[:-2])
for server in server_list:
file_name = '{0}{1}{2}{3}'.format(
base_path,
'/utils/scripts/opcode_handlers_output/',
server,
'_opcode_handlers.txt')
if DEBUG >= 1:
print(file_name)
out_files[server] = open(file_name, 'w')
message = \
'>> \'Opcode-Handler\' analysis for \'{0}\' server\n' \
'>> file generated @ {1}\n' \
'\n'.format(
server,
ctime(time()))
out_files[server].write(message)
if DEBUG >= 2:
print(message[:-2])
except:
if DEBUG >= 2:
print('EXCEPTION ERROR->openoutputfiles({0})'.format(sys.exc_info()[0]))
for client in client_list:
if client in out_files:
out_files[client].close()
del out_files[client]
if DEBUG >= 2:
print('->OpeningClientStream(exception): {0}'.format(client))
for server in server_list:
if server in out_files:
out_files[server].close()
del out_files[server]
if DEBUG >= 2:
print('->OpeningServerStream(exception): {0}'.format(server))
if 'Report' in out_files:
out_files['Report'].close()
del out_files['Report']
if DEBUG >= 2:
print('->OpeningReportStream(exception)')
return False
return True
def parseclientopcodedata():
# TODO: add metrics
for client in client_list:
server_max_len = 0
for server in server_list:
if len(server) > server_max_len:
server_max_len = len(server)
client_keys = client_opcodes[client].keys()
client_keys.sort()
for client_opcode in client_keys:
handled = client_opcode in server_opcodes
if handled is True:
encoded = client_opcode in client_encodes[client]
decoded = client_opcode in client_decodes[client]
else:
encoded = 'N/A'
decoded = 'N/A'
message = 'Opcode: {0} ({1}) | Handled: {2} | Encoded: {3} | Decoded: {4}\n'.format(
client_opcode,
client_opcodes[client][client_opcode],
handled,
encoded,
decoded)
for server in server_list:
if client_opcode in server_handlers[server] and len(server_handlers[server][client_opcode]) > 0:
handler_list = server_handlers[server][client_opcode]
handler_list.sort()
for handler_entry in handler_list:
message += ' Server: {0} ({1}) | Handler: {2}\n'.format(
server.ljust(len(server) + (server_max_len - len(server)), ' '),
'{0}'.format(server_opcodes[client_opcode]).zfill(4),
handler_entry)
else:
message += ' Server: {0} (0000) | Handler: N/A\n'.format(
server.ljust(len(server) + (server_max_len - len(server)), ' '))
if DEBUG >= 2:
print('->EndOfServerLoop: {0}'.format(server))
message += '\n'
out_files[client].write(message)
if DEBUG >= 2:
print(message[:-2])
print('->EndOfOpcodeLoop: {0}'.format(client_opcode))
if DEBUG >= 2:
print('->EndOfClientLoop: {0}'.format(client))
return True
def parseserveropcodedata():
# TODO: add metrics
for server in server_list:
client_max_len = 0
for client in client_list:
if len(client) > client_max_len:
client_max_len = len(client)
handler_keys = server_handlers[server].keys()
handler_keys.sort()
for handler_opcode in handler_keys:
handler_list = server_handlers[server][handler_opcode]
handler_list.sort()
message = ''
for handler_entry in handler_list:
message += 'Opcode: {0} ({1}) | Handler: {2}\n'.format(
handler_opcode,
server_opcodes[handler_opcode],
handler_entry)
for client in client_list:
if handler_opcode in client_opcodes[client]:
val1 = client_opcodes[client][handler_opcode]
val2 = 'True'
val3 = '{0}'.format(handler_opcode in client_encodes[client])
val4 = '{0}'.format(handler_opcode in client_decodes[client])
else:
val1 = '0x0000'
val2 = 'False'
val3 = 'N/A'
val4 = 'N/A'
message += ' Client: {0} ({1}) | Handled: {2} | Encoded: {3} | Decoded: {4}\n'.format(
client.ljust(len(client) + (client_max_len - len(client)), ' '),
val1,
val2.ljust(len(val2) + (len('False') - len(val2)), ' '),
val3.ljust(len(val3) + (len('False') - len(val3)), ' '),
val4.ljust(len(val4) + (len('False') - len(val4)), ' '))
if DEBUG >= 2:
print('->EndOfClientLoop: {0}'.format(client))
message += '\n'
out_files[server].write(message)
if DEBUG >= 2:
print(message[:-2])
print('->EndOfOpcodeLoop: {0}'.format(handler_opcode))
if DEBUG >= 2:
print('->EndOfServerLoop: {0}'.format(server))
return True
def closeoutputfiles():
for client in client_list:
if client in out_files:
out_files[client].close()
del out_files[client]
if DEBUG >= 2:
print('->ClosingClientStream: {0}'.format(client))
for server in server_list:
if server in out_files:
out_files[server].close()
del out_files[server]
if DEBUG >= 2:
print('->ClosingServerStream: {0}'.format(server))
if 'Report' in out_files:
out_files['Report'].close()
del out_files['Report']
if DEBUG >= 2:
print('->ClosingReportStream')
return True
def closedebugfile():
if 'DEBUG' in out_files:
out_files['DEBUG'].close()
del out_files['DEBUG']
if DEBUG >= 2:
print('->ClosingDEBUGStream')
return True
if __name__ == '__main__':
main()

View File

@ -0,0 +1,18 @@
-- spells new talbe update
ALTER TABLE `spells_new` CHANGE `NotOutofCombat` `InCombat` INT(11) NOT NULL DEFAULT '0';
ALTER TABLE `spells_new` CHANGE `NotInCombat` `OutofCombat` INT(11) NOT NULL DEFAULT '0';
ALTER TABLE `spells_new` CHANGE `field201` `viral_range` INT(11) NOT NULL DEFAULT '0';
ALTER TABLE `spells_new` CHANGE `field218` `aemaxtargets` INT(11) NOT NULL DEFAULT '0';
ALTER TABLE `spells_new` ADD `field225` int( 11 ) NOT NULL DEFAULT '0' AFTER `persistdeath`;
ALTER TABLE `spells_new` ADD `field226` int( 11 ) NOT NULL DEFAULT '0' AFTER `field225`;
ALTER TABLE `spells_new` ADD `min_dist` float( 0 ) NOT NULL DEFAULT '0' AFTER `field226`;
ALTER TABLE `spells_new` ADD `min_dist_mod` float( 0 ) NOT NULL DEFAULT '0' AFTER `min_dist`;
ALTER TABLE `spells_new` ADD `max_dist` float( 0 ) NOT NULL DEFAULT '0' AFTER `min_dist_mod`;
ALTER TABLE `spells_new` ADD `max_dist_mod` float( 0 ) NOT NULL DEFAULT '0' AFTER `max_dist`;
ALTER TABLE `spells_new` ADD `min_range` int( 11 ) NOT NULL DEFAULT '0' AFTER `max_dist_mod`;
ALTER TABLE `spells_new` ADD `field232` int( 11 ) NOT NULL DEFAULT '0' AFTER `min_range`;
ALTER TABLE `spells_new` ADD `field233` int( 11 ) NOT NULL DEFAULT '0' AFTER `field232`;
ALTER TABLE `spells_new` ADD `field234` int( 11 ) NOT NULL DEFAULT '0' AFTER `field233`;
ALTER TABLE `spells_new` ADD `field235` int( 11 ) NOT NULL DEFAULT '0' AFTER `field234`;
ALTER TABLE `spells_new` ADD `field236` int( 11 ) NOT NULL DEFAULT '0' AFTER `field235`;

View File

@ -249,6 +249,7 @@
#define CORPSEDRAG_BEGIN 4064 //You begin to drag %1.
#define CORPSEDRAG_STOPALL 4065 //You stop dragging the corpses.
#define CORPSEDRAG_STOP 4066 //You stop dragging the corpse.
#define TARGET_TOO_CLOSE 4602 //You are too close to your target. Get farther away.
#define WHOALL_NO_RESULTS 5029 //There are no players in EverQuest that match those who filters.
#define PETITION_NO_DELETE 5053 //You do not have a petition in the queue.
#define PETITION_DELETED 5054 //Your petition was successfully deleted.
@ -316,6 +317,10 @@
#define PET_NOT_FOCUSING 9263 //No longer focusing on one target, Master.
#define PET_NOT_CASTING 9264 //Not casting spells, Master.
#define PET_CASTING 9291 //Casting spells normally, Master.
#define NO_CAST_IN_COMBAT 9190 //You can not cast this spell while in combat.
#define NO_CAST_OUT_OF_COMBAT 9191 //You can not cast this spell while out of combat.
#define NO_ABILITY_IN_COMBAT 9192 //You can not use this ability while in combat.
#define NO_ABILITY_OUT_OF_COMBAT 9194 //You can not use this ability while out of combat.
#define AE_RAMPAGE 11015 //%1 goes on a WILD RAMPAGE!
#define FACE_ACCEPTED 12028 //Facial features accepted.
#define SPELL_LEVEL_TO_LOW 12048 //You will have to achieve level %1 before you can scribe the %2.

View File

@ -311,7 +311,7 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c
hitBonus += (attacker->CastToNPC()->GetAccuracyRating() / 10.0f); //Modifier from database
if(skillinuse == SkillArchery)
hitBonus -= hitBonus*(RuleR(Combat, ArcheryHitPenalty)*100.0f);
hitBonus -= hitBonus*RuleR(Combat, ArcheryHitPenalty);
//Calculate final chance to hit
chancetohit += ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0f);

View File

@ -740,6 +740,8 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_
float dist = caster->GetAOERange(spell_id);
float dist2 = dist * dist;
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
float dist_targ = 0;
bool bad = IsDetrimentalSpell(spell_id);
bool isnpc = caster->IsNPC();
@ -755,7 +757,11 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_
continue;
if (curmob == caster && !affect_caster) //watch for caster too
continue;
if (center->DistNoRoot(*curmob) > dist2) //make sure they are in range
dist_targ = center->DistNoRoot(*curmob);
if (dist_targ > dist2) //make sure they are in range
continue;
if (dist_targ < min_range2) //make sure they are in range
continue;
if (isnpc && curmob->IsNPC()) { //check npc->npc casting
FACTION_VALUE f = curmob->GetReverseFactionCon(caster);
@ -786,6 +792,8 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_
continue;
}
curmob->CalcSpellPowerDistanceMod(spell_id, dist_targ);
//if we get here... cast the spell.
if (IsTargetableAESpell(spell_id) && bad) {
if (iCounter < MAX_TARGETS_ALLOWED) {

View File

@ -4552,7 +4552,7 @@ Mob *EntityList::GetClosestMobByBodyType(Mob *sender, bodyType BodyType)
return ClosestMob;
}
void EntityList::GetTargetsForConeArea(Mob *start, uint32 radius, uint32 height, std::list<Mob*> &m_list)
void EntityList::GetTargetsForConeArea(Mob *start, float min_radius, float radius, float height, std::list<Mob*> &m_list)
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
@ -4561,15 +4561,15 @@ void EntityList::GetTargetsForConeArea(Mob *start, uint32 radius, uint32 height,
++it;
continue;
}
int32 x_diff = ptr->GetX() - start->GetX();
int32 y_diff = ptr->GetY() - start->GetY();
int32 z_diff = ptr->GetZ() - start->GetZ();
float x_diff = ptr->GetX() - start->GetX();
float y_diff = ptr->GetY() - start->GetY();
float z_diff = ptr->GetZ() - start->GetZ();
x_diff *= x_diff;
y_diff *= y_diff;
z_diff *= z_diff;
if ((x_diff + y_diff) <= (radius * radius))
if ((x_diff + y_diff) <= (radius * radius) && (x_diff + y_diff) >= (min_radius * min_radius))
if(z_diff <= (height * height))
m_list.push_back(ptr);

View File

@ -406,7 +406,7 @@ public:
void GetObjectList(std::list<Object*> &o_list);
void GetDoorsList(std::list<Doors*> &d_list);
void GetSpawnList(std::list<Spawn2*> &d_list);
void GetTargetsForConeArea(Mob *start, uint32 radius, uint32 height, std::list<Mob*> &m_list);
void GetTargetsForConeArea(Mob *start, float min_radius, float radius, float height, std::list<Mob*> &m_list);
void DepopAll(int NPCTypeID, bool StartSpawnTimer = true);

View File

@ -658,6 +658,7 @@ void Group::CastGroupSpell(Mob* caster, uint16 spell_id) {
range = caster->GetAOERange(spell_id);
float range2 = range*range;
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
// caster->SpellOnTarget(spell_id, caster);
@ -673,7 +674,8 @@ void Group::CastGroupSpell(Mob* caster, uint16 spell_id) {
else if(members[z] != nullptr)
{
distance = caster->DistNoRoot(*members[z]);
if(distance <= range2) {
if(distance <= range2 && distance >= min_range2) {
members[z]->CalcSpellPowerDistanceMod(spell_id, distance);
caster->SpellOnTarget(spell_id, members[z]);
#ifdef GROUP_BUFF_PETS
if(members[z]->GetPet() && members[z]->HasPetAffinity() && !members[z]->GetPet()->IsCharmed())

View File

@ -567,20 +567,25 @@ void HateList::SpellCast(Mob *caster, uint32 spell_id, float range)
//So keep a list of entity ids and look up after
std::list<uint32> id_list;
range = range * range;
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
float dist_targ = 0;
auto iterator = list.begin();
while (iterator != list.end())
{
tHateEntry *h = (*iterator);
if(range > 0)
{
if(caster->DistNoRoot(*h->ent) <= range)
dist_targ = caster->DistNoRoot(*h->ent);
if(dist_targ <= range && dist_targ >= min_range2)
{
id_list.push_back(h->ent->GetID());
h->ent->CalcSpellPowerDistanceMod(spell_id, dist_targ);
}
}
else
{
id_list.push_back(h->ent->GetID());
h->ent->CalcSpellPowerDistanceMod(spell_id, 0, caster);
}
++iterator;
}

View File

@ -184,6 +184,7 @@ Mob::Mob(const char* in_name,
has_numhits = false;
has_MGB = false;
has_ProjectIllusion = false;
SpellPowerDistanceMod = 0;
if(in_aa_title>0)
aa_title = in_aa_title;

View File

@ -615,6 +615,9 @@ public:
bool ImprovedTaunt();
bool TryRootFadeByDamage(int buffslot, Mob* attacker);
int16 GetSlowMitigation() const {return slow_mitigation;}
void CalcSpellPowerDistanceMod(uint16 spell_id, float range, Mob* caster = nullptr);
inline int16 GetSpellPowerDistanceMod() const { return SpellPowerDistanceMod; };
inline void SetSpellPowerDistanceMod(int16 value) { SpellPowerDistanceMod = value; };
void ModSkillDmgTaken(SkillUseTypes skill_num, int value);
int16 GetModSkillDmgTaken(const SkillUseTypes skill_num);
@ -1114,6 +1117,7 @@ protected:
bool has_numhits;
bool has_MGB;
bool has_ProjectIllusion;
int16 SpellPowerDistanceMod;
// Bind wound
Timer bindwound_timer;

View File

@ -186,6 +186,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
buffs[buffslot].numhits = numhit;
}
if (!IsPowerDistModSpell(spell_id))
SetSpellPowerDistanceMod(0);
// iterate through the effects in the spell
for (i = 0; i < EFFECT_COUNT; i++)
{
@ -198,6 +201,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands))
effect_value = GetMaxHP();
if (GetSpellPowerDistanceMod())
effect_value = effect_value*(GetSpellPowerDistanceMod()/100);
#ifdef SPELL_EFFECT_SPAM
effect_desc[0] = 0;
#endif
@ -2705,7 +2711,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
if (buffslot >= 0)
break;
if(IsCasting() && MakeRandomInt(0, 100) <= spells[spell_id].base[i])
if(!spells[spell_id].uninterruptable && IsCasting() && MakeRandomInt(0, 100) <= spells[spell_id].base[i])
InterruptSpell();
break;
@ -6444,4 +6450,24 @@ bool Mob::CheckSpellCategory(uint16 spell_id, int category_id, int effect_id){
return false;
}
void Mob::CalcSpellPowerDistanceMod(uint16 spell_id, float range, Mob* caster)
{
if (IsPowerDistModSpell(spell_id)){
float distance = 0;
if (caster && !range)
distance = caster->CalculateDistance(GetX(), GetY(), GetZ());
else
distance = sqrt(range);
float dm_range = spells[spell_id].max_dist - spells[spell_id].min_dist;
float dm_mod_interval = spells[spell_id].max_dist_mod - spells[spell_id].min_dist_mod;
float dist_from_min = distance - spells[spell_id].min_dist;
float mod = spells[spell_id].min_dist_mod + (dist_from_min * (dm_mod_interval/dm_range));
mod *= 100.0f;
SetSpellPowerDistanceMod(static_cast<int>(mod));
}
}

View File

@ -1034,7 +1034,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot,
mlog(SPELLS__CASTING, "Checking Interruption: spell x: %f spell y: %f cur x: %f cur y: %f channelchance %f channeling skill %d\n", GetSpellX(), GetSpellY(), GetX(), GetY(), channelchance, GetSkill(SkillChanneling));
if(MakeRandomFloat(0, 100) > channelchance) {
if(!spells[spell_id].uninterruptable && MakeRandomFloat(0, 100) > channelchance) {
mlog(SPELLS__CASTING_ERR, "Casting of %d canceled: interrupted.", spell_id);
InterruptSpell();
return;
@ -1384,6 +1384,52 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce
return false;
}
//Must be out of combat. (If Beneficial checks casters combat state, Deterimental checks targets)
if (!spells[spell_id].InCombat && spells[spell_id].OutofCombat){
if (IsDetrimentalSpell(spell_id)) {
if ( (spell_target->IsNPC() && spell_target->IsEngaged()) ||
(spell_target->IsClient() && spell_target->CastToClient()->GetAggroCount())){
Message_StringID(13,SPELL_NO_EFFECT); //Unsure correct string
return false;
}
}
else if (IsBeneficialSpell(spell_id)) {
if ( (IsNPC() && IsEngaged()) ||
(IsClient() && CastToClient()->GetAggroCount())){
if (IsDiscipline(spell_id))
Message_StringID(13,NO_ABILITY_IN_COMBAT);
else
Message_StringID(13,NO_CAST_IN_COMBAT);
return false;
}
}
}
//Must be in combat. (If Beneficial checks casters combat state, Deterimental checks targets)
else if (spells[spell_id].InCombat && !spells[spell_id].OutofCombat){
if (IsDetrimentalSpell(spell_id)) {
if ( (spell_target->IsNPC() && !spell_target->IsEngaged()) ||
(spell_target->IsClient() && !spell_target->CastToClient()->GetAggroCount())){
Message_StringID(13,SPELL_NO_EFFECT); //Unsure correct string
return false;
}
}
else if (IsBeneficialSpell(spell_id)) {
if ( (IsNPC() && !IsEngaged()) ||
(IsClient() && !CastToClient()->GetAggroCount())){
if (IsDiscipline(spell_id))
Message_StringID(13,NO_ABILITY_OUT_OF_COMBAT);
else
Message_StringID(13,NO_CAST_OUT_OF_COMBAT);
return false;
}
}
}
switch (targetType)
{
// single target spells
@ -1734,6 +1780,24 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce
break;
}
case ST_PetMaster:
{
Mob *owner = nullptr;
if (IsPet())
owner = GetOwner();
else if ((IsNPC() && CastToNPC()->GetSwarmOwner()))
owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
if (!owner)
return false;
spell_target = owner;
CastAction = SingleTarget;
break;
}
default:
{
mlog(SPELLS__CASTING_ERR, "I dont know Target Type: %d Spell: (%d) %s", spells[spell_id].targettype, spell_id, spells[spell_id].name);
@ -1863,12 +1927,21 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
//casting a spell on somebody but ourself, make sure they are in range
float dist2 = DistNoRoot(*spell_target);
float range2 = range * range;
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
if(dist2 > range2) {
//target is out of range.
mlog(SPELLS__CASTING, "Spell %d: Spell target is out of range (squared: %f > %f)", spell_id, dist2, range2);
Message_StringID(13, TARGET_OUT_OF_RANGE);
return(false);
}
else if (dist2 < min_range2){
//target is too close range.
mlog(SPELLS__CASTING, "Spell %d: Spell target is too close (squared: %f < %f)", spell_id, dist2, min_range2);
Message_StringID(13, TARGET_TOO_CLOSE);
return(false);
}
spell_target->CalcSpellPowerDistanceMod(spell_id, dist2);
}
//
@ -2052,7 +2125,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
std::list<Mob*> targets_in_range;
std::list<Mob*>::iterator iter;
entity_list.GetTargetsForConeArea(this, spells[spell_id].aoerange, spells[spell_id].aoerange / 2, targets_in_range);
entity_list.GetTargetsForConeArea(this, spells[spell_id].min_range, spells[spell_id].aoerange, spells[spell_id].aoerange / 2, targets_in_range);
iter = targets_in_range.begin();
while(iter != targets_in_range.end())
{
@ -2068,16 +2141,20 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
if((heading_to_target >= angle_start && heading_to_target <= 360.0f) ||
(heading_to_target >= 0.0f && heading_to_target <= angle_end))
{
if(CheckLosFN(spell_target))
if(CheckLosFN((*iter)) || spells[spell_id].npc_no_los){
(*iter)->CalcSpellPowerDistanceMod(spell_id, 0, this);
SpellOnTarget(spell_id, spell_target, false, true, resist_adjust);
}
}
}
else
{
if(heading_to_target >= angle_start && heading_to_target <= angle_end)
{
if(CheckLosFN((*iter)))
if(CheckLosFN((*iter)) || spells[spell_id].npc_no_los){
(*iter)->CalcSpellPowerDistanceMod(spell_id, 0, this);
SpellOnTarget(spell_id, (*iter), false, true, resist_adjust);
}
}
}
++iter;
@ -4625,12 +4702,10 @@ void Mob::Stun(int duration)
if(stunned && stunned_timer.GetRemainingTime() > uint32(duration))
return;
if(casting_spell_id) {
int persistent_casting = spellbonuses.PersistantCasting + itembonuses.PersistantCasting;
if(IsClient())
persistent_casting += aabonuses.PersistantCasting;
if(IsValidSpell(casting_spell_id) && !spells[casting_spell_id].uninterruptable) {
int persistent_casting = spellbonuses.PersistantCasting + itembonuses.PersistantCasting + aabonuses.PersistantCasting;
if(MakeRandomInt(1,99) > persistent_casting)
if(MakeRandomInt(0,99) > persistent_casting)
InterruptSpell();
}