From eabea6ea16c2db3af2152e4a6260c77210e629c8 Mon Sep 17 00:00:00 2001 From: KimLS Date: Sun, 23 Feb 2014 17:14:50 -0800 Subject: [PATCH 01/37] RaycastMesh work der started I'm going to pick up... --- zone/CMakeLists.txt | 2 + zone/Map.cpp | 1025 ------------------------------------------ zone/RaycastMesh.cpp | 938 ++++++++++++++++++++++++++++++++++++++ zone/RaycastMesh.h | 60 +++ zone/map.h | 190 -------- 5 files changed, 1000 insertions(+), 1215 deletions(-) delete mode 100644 zone/Map.cpp create mode 100644 zone/RaycastMesh.cpp create mode 100644 zone/RaycastMesh.h delete mode 100644 zone/map.h diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 49410f0f2..68c4678f3 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -95,6 +95,7 @@ SET(zone_sources questmgr.cpp QuestParserCollection.cpp raids.cpp + RaycastMesh.cpp spawn2.cpp spawn2.h spawngroup.cpp @@ -183,6 +184,7 @@ SET(zone_headers QuestParserCollection.h raid.h raids.h + RaycastMesh.h skills.h spawn2.cpp spawn2.h diff --git a/zone/Map.cpp b/zone/Map.cpp deleted file mode 100644 index 95a6ba1a5..000000000 --- a/zone/Map.cpp +++ /dev/null @@ -1,1025 +0,0 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) - - 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 -*/ -#include "../common/debug.h" -#include "../common/MiscFunctions.h" -#include -#include -#include -#include - -#ifndef WIN32 -//comment this out if your worried about zone boot times and your not using valgrind -#define SLOW_AND_CRAPPY_MAKES_VALGRIND_HAPPY -#endif - -#include "map.h" -#include "zone.h" -#ifdef _WINDOWS -#define snprintf _snprintf -#endif - -extern Zone* zone; - -//Do we believe the normals from the map file? -//you want this enabled if it dosent break things. -//#define TRUST_MAPFILE_NORMALS - -//#define OPTIMIZE_QT_LOOKUPS -#define EPS 0.002f //acceptable error - -//#define DEBUG_SEEK 1 -//#define DEBUG_BEST_Z 1 - -/* - of note: - it is possible to get a null node in a valid region if it - does not have any triangles in it. - this will dictate bahaviour on getting a null node - TODO: listen to the above - */ - -//quick functions to clean up vertex code. -#define Vmin3(o, a, b, c) ((a.ob.o)? (a.o>c.o?a.o:c.o) : (b.o>c.o?b.o:c.o)) -#define ABS(x) ((x)<0?-(x):(x)) - -Map* Map::LoadMapfile(const char* in_zonename, const char *directory) { - FILE *fp; - char zBuf[64]; - char cWork[256]; - Map* ret = 0; - - //have to convert to lower because the short names im getting - //are not all lower anymore, copy since strlwr edits the str. - strn0cpy(zBuf, in_zonename, 64); - - if(directory == nullptr) - directory = MAP_DIR; - snprintf(cWork, 250, "%s/%s.map", directory, strlwr(zBuf)); - - if ((fp = fopen( cWork, "rb" ))) { - ret = new Map(); - if(ret != nullptr) { - ret->loadMap(fp); - printf("Map %s loaded.\n", cWork); - } else { - printf("Map %s loading failed.\n", cWork); - } - fclose(fp); - } - else { - printf("Map %s not found.\n", cWork); - } - return ret; -} - -Map::Map() { - _minz = FLT_MAX; - _minx = FLT_MAX; - _miny = FLT_MAX; - _maxz = FLT_MIN; - _maxx = FLT_MIN; - _maxy = FLT_MIN; - - m_Faces = 0; - m_Nodes = 0; - m_FaceLists = 0; - mFinalFaces = nullptr; - mNodes = nullptr; - mFaceLists = nullptr; -} - -bool Map::loadMap(FILE *fp) { -#ifndef INVERSEXY -#warning Map files do not work without inverted XY - return(false); -#endif - - mapHeader head; - if(fread(&head, sizeof(head), 1, fp) != 1) { - //map read error. - return(false); - } - if(head.version != MAP_VERSION) { - //invalid version... if there really are multiple versions, - //a conversion routine could be possible. - printf("Invalid map version 0x%lx\n", (unsigned long)head.version); - return(false); - } - - printf("Map header: %lu faces, %u nodes, %lu facelists\n", (unsigned long)head.face_count, (unsigned int)head.node_count, (unsigned long)head.facelist_count); - - m_Faces = head.face_count; - m_Nodes = head.node_count; - m_FaceLists = head.facelist_count; - - /* fread(&m_Vertex, 4, 1, fp); - fread(&m_Faces , 4, 1, fp);*/ -// mFinalVertex = new VERTEX[m_Vertex]; - mFinalFaces = new FACE [m_Faces]; - mNodes = new NODE[m_Nodes]; - mFaceLists = new uint32[m_FaceLists]; - -// fread(mFinalVertex, m_Vertex, sizeof(VERTEX), fp); - - //this was changed to this loop from the single read because valgrind was - //hanging on this read otherwise... I dont pretend to understand it. -#ifdef SLOW_AND_CRAPPY_MAKES_VALGRIND_HAPPY - uint32 r; - for(r = 0; r < m_Faces; r++) { - if(fread(mFinalFaces+r, sizeof(FACE), 1, fp) != 1) { - printf("Unable to read %lu faces from map file, got %lu.\n", (unsigned long)m_Faces, (unsigned long)r); - return(false); - } - } -#else - uint32 count; - if((count = static_cast(fread(mFinalFaces, sizeof(FACE), m_Faces , fp))) != m_Faces) { - printf("Unable to read %lu face bytes from map file, got %lu.\n", (unsigned long)m_Faces, (unsigned long)count); - return(false); - } -#endif - -#ifdef SLOW_AND_CRAPPY_MAKES_VALGRIND_HAPPY - for(r = 0; r < m_Nodes; r++) { - if(fread(mNodes+r, sizeof(NODE), 1, fp) != 1) { - printf("Unable to read %lu nodes from map file, got %lu.\n", (unsigned long)m_Nodes, (unsigned long)r); - return(false); - } - } -#else - if(fread(mNodes, sizeof(NODE), m_Nodes, fp) != m_Nodes) { - printf("Unable to read %lu nodes from map file.\n", (unsigned long)m_Nodes); - return(false); - } -#endif - -#ifdef SLOW_AND_CRAPPY_MAKES_VALGRIND_HAPPY - for(r = 0; r < m_FaceLists; r++) { - if(fread(mFaceLists+r, sizeof(uint32), 1, fp) != 1) { - printf("Unable to read %lu face lists from map file, got %lu.\n", (unsigned long)m_FaceLists, (unsigned long)r); - return(false); - } - } -#else - if(fread(mFaceLists, sizeof(uint32), m_FaceLists, fp) != m_FaceLists) { - printf("Unable to read %lu face lists from map file.\n", (unsigned long)m_FaceLists); - return(false); - } -#endif - - -/* mRoot = new NODE(); - RecLoadNode(mRoot, fp );*/ - - uint32 i; - float v; - for(i = 0; i < m_Faces; i++) { - v = Vmax3(x, mFinalFaces[i].a, mFinalFaces[i].b, mFinalFaces[i].c); - if(v > _maxx) - _maxx = v; - v = Vmin3(x, mFinalFaces[i].a, mFinalFaces[i].b, mFinalFaces[i].c); - if(v < _minx) - _minx = v; - v = Vmax3(y, mFinalFaces[i].a, mFinalFaces[i].b, mFinalFaces[i].c); - if(v > _maxy) - _maxy = v; - v = Vmin3(y, mFinalFaces[i].a, mFinalFaces[i].b, mFinalFaces[i].c); - if(v < _miny) - _miny = v; - v = Vmax3(z, mFinalFaces[i].a, mFinalFaces[i].b, mFinalFaces[i].c); - if(v > _maxz) - _maxz = v; - v = Vmin3(z, mFinalFaces[i].a, mFinalFaces[i].b, mFinalFaces[i].c); - if(v < _minz) - _minz = v; - } - printf("Loaded map: %lu vertices, %lu faces\n", (unsigned long)m_Faces*3, (unsigned long)m_Faces); - printf("Map BB: (%.2f -> %.2f, %.2f -> %.2f, %.2f -> %.2f)\n", _minx, _maxx, _miny, _maxy, _minz, _maxz); - return(true); -} - -Map::~Map() { -// safe_delete_array(mFinalVertex); - safe_delete_array(mFinalFaces); - safe_delete_array(mNodes); - safe_delete_array(mFaceLists); -// RecFreeNode( mRoot ); -} - - -NodeRef Map::SeekNode( NodeRef node_r, float x, float y ) const { - if(node_r == NODE_NONE || node_r >= m_Nodes) { - return(NODE_NONE); - } - PNODE _node = &mNodes[node_r]; -#ifdef DEBUG_SEEK -printf("Seeking node for %u:(%.2f, %.2f) with root 0x%x.\n", node_r, x, y, _node); - -printf(" Current Box: (%.2f -> %.2f, %.2f -> %.2f)\n", _node->minx, _node->maxx, _node->miny, _node->maxy); -#endif - if( x>= _node->minx && x<= _node->maxx && y>= _node->miny && y<= _node->maxy ) { - if( _node->flags & nodeFinal ) { -#ifdef DEBUG_SEEK -printf("Seeking node for %u:(%.2f, %.2f) with root 0x%x.\n", node_r, x, y, _node); -printf(" Final Node: (%.2f -> %.2f, %.2f -> %.2f)\n", _node->minx, _node->maxx, _node->miny, _node->maxy); -fflush(stdout); -printf(" Final node found with %d faces.\n", _node->faces.count); -/*printf(" Faces:\n"); -unsigned long *cfl = mFaceLists + _node->faces.offset; -unsigned long m; -for(m = 0; m < _node->faces.count; m++) { - FACE *c = &mFinalFaces[ *cfl ]; - printf(" %lu (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)\n", - *cfl, c->a.x, c->a.y, c->a.z, - c->b.x, c->b.y, c->b.z, - c->c.x, c->c.y, c->c.z); - cfl++; -}*/ -#endif - return node_r; - } -#ifdef DEBUG_SEEK -printf(" Kids: %u, %u, %u, %u\n", _node->nodes[0], _node->nodes[1], _node->nodes[2], _node->nodes[3]); - -printf(" Contained In Box: (%.2f -> %.2f, %.2f -> %.2f)\n", _node->minx, _node->maxx, _node->miny, _node->maxy); - -/*printf(" Node found has children.\n"); -if(_node->node1 != nullptr) { - printf("\tNode: (%.2f -> %.2f, %.2f -> %.2f)\n", - _node->node1->minx, _node->node1->maxx, _node->node1->miny, _node->node1->maxy); -} -if(_node->node2 != nullptr) { - printf("\tNode: (%.2f -> %.2f, %.2f -> %.2f)\n", - _node->node2->minx, _node->node2->maxx, _node->node2->miny, _node->node2->maxy); -} -if(_node->node3 != nullptr) { - printf("\tNode: (%.2f -> %.2f, %.2f -> %.2f)\n", - _node->node3->minx, _node->node3->maxx, _node->node3->miny, _node->node3->maxy); -} -if(_node->node4 != nullptr) { - printf("\tNode: (%.2f -> %.2f, %.2f -> %.2f)\n", - _node->node4->minx, _node->node4->maxx, _node->node4->miny, _node->node4->maxy); -}*/ -#endif - //NOTE: could precalc these and store them in node headers - - NodeRef tmp = NODE_NONE; -#ifdef OPTIMIZE_QT_LOOKUPS - float midx = _node->minx + (_node->maxx - _node->minx) * 0.5; - float midy = _node->miny + (_node->maxy - _node->miny) * 0.5; - //follow ordering rules from map.h... - if(x < midx) { - if(y < midy) { //quad 3 - if(_node->nodes[2] != NODE_NONE && _node->nodes[2] != node_r) - tmp = SeekNode( _node->nodes[2], x, y ); - } else { //quad 2 - if(_node->nodes[2] != NODE_NONE && _node->nodes[1] != node_r) - tmp = SeekNode( _node->nodes[1], x, y ); - } - } else { - if(y < midy) { //quad 4 - if(_node->nodes[2] != NODE_NONE && _node->nodes[3] != node_r) - tmp = SeekNode( _node->nodes[3], x, y ); - } else { //quad 1 - if(_node->nodes[2] != NODE_NONE && _node->nodes[0] != node_r) - tmp = SeekNode( _node->nodes[0], x, y ); - } - } - if( tmp != NODE_NONE ) return tmp; -#else - if(_node->nodes[0] == node_r) return(NODE_NONE); //prevent infinite recursion - tmp = SeekNode( _node->nodes[0], x, y ); - if( tmp != NODE_NONE ) return tmp; - if(_node->nodes[1] == node_r) return(NODE_NONE); //prevent infinite recursion - tmp = SeekNode( _node->nodes[1], x, y ); - if( tmp != NODE_NONE ) return tmp; - if(_node->nodes[2] == node_r) return(NODE_NONE); //prevent infinite recursion - tmp = SeekNode( _node->nodes[2], x, y ); - if( tmp != NODE_NONE ) return tmp; - if(_node->nodes[3] == node_r) return(NODE_NONE); //prevent infinite recursion - tmp = SeekNode( _node->nodes[3], x, y ); - if( tmp != NODE_NONE ) return tmp; -#endif - - } -#ifdef DEBUG_SEEK -printf(" No node found.\n"); -#endif - return(NODE_NONE); -} - -// maybe precalc edges. -int* Map::SeekFace( NodeRef node_r, float x, float y ) { - if( node_r == NODE_NONE || node_r >= m_Nodes) { - return(nullptr); - } - const PNODE _node = &mNodes[node_r]; - if(!(_node->flags & nodeFinal)) { - return(nullptr); //not a final node... could find the proper node... - } - - -//printf("Seeking face for (%.2f, %.2f) with root 0x%x.\n", x, y, _node); - float dx,dy; - float nx,ny; - int *face = mCandFaces; - unsigned long i; - for( i=0;i<_node->faces.count;i++ ) { - const FACE &cf = mFinalFaces[ mFaceLists[_node->faces.offset + i] ]; - const VERTEX &v1 = cf.a; - const VERTEX &v2 = cf.b; - const VERTEX &v3 = cf.c; - - dx = v2.x - v1.x; dy = v2.y - v1.y; - nx = x - v1.x; ny = y - v1.y; - if( dx*ny - dy*nx >0.0f ) continue; - - dx = v3.x - v2.x; dy = v3.y - v2.y; - nx = x - v2.x; ny = y - v2.y; - if( dx*ny - dy*nx >0.0f ) continue; - - dx = v1.x - v3.x; dy = v1.y - v3.y; - nx = x - v3.x; ny = y - v3.y; - if( dx*ny - dy*nx >0.0f ) continue; - - *face++ = mFaceLists[_node->faces.offset + i]; - } - *face = -1; - return mCandFaces; -} - -// can be op? -float Map::GetFaceHeight( int _idx, float x, float y ) const { - const PFACE pface = &mFinalFaces[ _idx ]; - return ( pface->nd - x * pface->nx - y * pface->ny ) / pface->nz; -} - -//FatherNitwit's LOS code... -//Algorithm stolen from internet -//p1=start of segment -//p2=end of segment - -bool Map::LineIntersectsZone(VERTEX start, VERTEX end, float step_mag, VERTEX *result, FACE **on) const -{ - // Cast a ray from start to end, checking for collisions in each node between the two points. - // - float stepx, stepy, stepz, curx, cury, curz; - - curx = start.x; - cury = start.y; - curz = start.z; - - VERTEX cur = start; - - stepx = end.x - start.x; - stepy = end.y - start.y; - stepz = end.z - start.z; - - if((stepx == 0) && (stepy == 0) && (stepz == 0)) - return false; - - float factor = sqrt(stepx*stepx + stepy*stepy + stepz*stepz); - - stepx = (stepx/factor)*step_mag; - stepy = (stepy/factor)*step_mag; - stepz = (stepz/factor)*step_mag; - - NodeRef cnode, lnode, finalnode; - lnode = NODE_NONE; //last node visited - - cnode = SeekNode(GetRoot(), start.x, start.y); - finalnode = SeekNode(GetRoot(), end.x, end.y); - if(cnode == finalnode) - return LineIntersectsNode(cnode, start, end, result, on); - - do { - - stepx = (float)end.x - curx; - stepy = (float)end.y - cury; - stepz = (float)end.z - curz; - - factor = sqrt(stepx*stepx + stepy*stepy + stepz*stepz); - - stepx = (stepx/factor)*step_mag; - stepy = (stepy/factor)*step_mag; - stepz = (stepz/factor)*step_mag; - - cnode = SeekNode(GetRoot(), curx, cury); - if(cnode != lnode) - { - lnode = cnode; - - if(cnode == NODE_NONE) - return false; - - if(LineIntersectsNode(cnode, start, end, result, on)) - return(true); - - if(cnode == finalnode) - return false; - } - curx += stepx; - cury += stepy; - curz += stepz; - - cur.x = curx; - cur.y = cury; - cur.z = curz; - - if(ABS(curx - end.x) < step_mag) cur.x = end.x; - if(ABS(cury - end.y) < step_mag) cur.y = end.y; - if(ABS(curz - end.z) < step_mag) cur.z = end.z; - - } while(cur.x != end.x || cur.y != end.y || cur.z != end.z); - - return false; -} - -bool Map::LocWithinNode( NodeRef node_r, float x, float y ) const { - if( node_r == NODE_NONE || node_r >= m_Nodes) { - return(false); - } - const PNODE _node = &mNodes[node_r]; - //this function exists so nobody outside of MAP needs to know - //how the NODE sturcture works - return( x>= _node->minx && x<= _node->maxx && y>= _node->miny && y<= _node->maxy ); -} - -bool Map::LineIntersectsNode( NodeRef node_r, VERTEX p1, VERTEX p2, VERTEX *result, FACE **on) const { - if( node_r == NODE_NONE || node_r >= m_Nodes) { - return(true); //can see through empty nodes, just allow LOS on error... - } - const PNODE _node = &mNodes[node_r]; - if(!(_node->flags & nodeFinal)) { - return(true); //not a final node... not sure best action - } - - unsigned long i; - - PFACE cur; - const uint32 *cfl = mFaceLists + _node->faces.offset; - - for(i = 0; i < _node->faces.count; i++) { - if(*cfl > m_Faces) - continue; //watch for invalid lists, they seem to happen - cur = &mFinalFaces[ *cfl ]; - if(LineIntersectsFace(cur,p1, p2, result)) { - if(on != nullptr) - *on = cur; - return(true); - } - cfl++; - } - -//printf("Checked %ld faces and found none in the way.\n", i); - - return(false); -} - - -float Map::FindBestZ( NodeRef node_r, VERTEX p1, VERTEX *result, FACE **on) const { - - p1.z += RuleI(Map, FindBestZHeightAdjust); - - if(RuleB(Map, UseClosestZ)) - return FindClosestZ(p1); - - if(node_r == GetRoot()) { - node_r = SeekNode(node_r, p1.x, p1.y); - } - if( node_r == NODE_NONE || node_r >= m_Nodes) { - return(BEST_Z_INVALID); - } - const PNODE _node = &mNodes[node_r]; - if(!(_node->flags & nodeFinal)) { - return(BEST_Z_INVALID); //not a final node... could find the proper node... - } - - VERTEX tmp_result; //dummy placeholder if they do not ask for a result. - if(result == nullptr) - result = &tmp_result; - - VERTEX p2(p1); - p2.z = BEST_Z_INVALID; - - float best_z = BEST_Z_INVALID; - int zAttempt; - - unsigned long i; - - PFACE cur; - - // If we don't find a bestZ on the first attempt, we try again from a position CurrentZ + 10 higher - // This is in case the pathing between waypoints temporarily sends the NPC below ground level. - // - for(zAttempt=1; zAttempt<=2; zAttempt++) { - - const uint32 *cfl = mFaceLists + _node->faces.offset; - -#ifdef DEBUG_BEST_Z -printf("Start finding best Z...\n"); -#endif - for(i = 0; i < _node->faces.count; i++) { - if(*cfl > m_Faces) - continue; //watch for invalid lists, they seem to happen, e.g. in eastwastes.map - - cur = &mFinalFaces[ *cfl ]; -//printf("Intersecting with face %lu\n", *cfl); - if(LineIntersectsFace(cur, p1, p2, result)) { -#ifdef DEBUG_BEST_Z - printf(" %lu (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)\n", - *cfl, cur->a.x, cur->a.y, cur->a.z, - cur->b.x, cur->b.y, cur->b.z, - cur->c.x, cur->c.y, cur->c.z); - printf("Found a z: %.2f\n", result->z); -#endif - if (result->z > best_z) { - if(on != nullptr) - *on = cur; - best_z = result->z; - } - } - cfl++; - } - - if(best_z != BEST_Z_INVALID) return best_z; - - p1.z = p1.z + 10 ; // If we can't find a best Z, the NPC is probably just under the world. Try again from 10 units higher up. - } - -#ifdef DEBUG_BEST_Z -fflush(stdout); -printf("Best Z found: %.2f\n", best_z); -#endif - return best_z; -} - - -bool Map::LineIntersectsFace( PFACE cface, VERTEX p1, VERTEX p2, VERTEX *result) const { - if( cface == nullptr ) { - return(false); //cant intersect a face we dont have... i guess - } - - const VERTEX &pa = cface->a; - const VERTEX &pb = cface->b; - const VERTEX &pc = cface->c; - - //quick bounding box checks - float tbb; - - tbb = Vmin3(x, pa, pb, pc); - if(p1.x < tbb && p2.x < tbb) - return(false); - tbb = Vmin3(y, pa, pb, pc); - if(p1.y < tbb && p2.y < tbb) - return(false); - tbb = Vmin3(z, pa, pb, pc); - if(p1.z < tbb && p2.z < tbb) - return(false); - - tbb = Vmax3(x, pa, pb, pc); - if(p1.x > tbb && p2.x > tbb) - return(false); - tbb = Vmax3(y, pa, pb, pc); - if(p1.y > tbb && p2.y > tbb) - return(false); - tbb = Vmax3(z, pa, pb, pc); - if(p1.z > tbb && p2.z > tbb) - return(false); - - //begin attempt 2 -//#define RTOD 57.2957795 //radians to degrees constant. - - float d; - float denom,mu; - VERTEX n, intersect; - -// FACE *thisface = &mFinalFaces[ _node->pfaces[ i ] ]; - - VERTEX *p = &intersect; - if(result != nullptr) - p = result; - - // Calculate the parameters for the plane - //recalculate from points -#ifndef TRUST_MAPFILE_NORMALS - n.x = (pb.y - pa.y)*(pc.z - pa.z) - (pb.z - pa.z)*(pc.y - pa.y); - n.y = (pb.z - pa.z)*(pc.x - pa.x) - (pb.x - pa.x)*(pc.z - pa.z); - n.z = (pb.x - pa.x)*(pc.y - pa.y) - (pb.y - pa.y)*(pc.x - pa.x); - Normalize(&n); - d = - n.x * pa.x - n.y * pa.y - n.z * pa.z; -#else - //use precaled data from .map file - n.x = cface->nx; - n.y = cface->ny; - n.z = cface->nz; - d = cface->nd; -#endif - - //try inverting the normals... - n.x = -n.x; - n.y = -n.y; - n.z = -n.z; - d = - n.x * pa.x - n.y * pa.y - n.z * pa.z; //recalc - - - // Calculate the position on the line that intersects the plane - denom = n.x * (p2.x - p1.x) + n.y * (p2.y - p1.y) + n.z * (p2.z - p1.z); - if (ABS(denom) < EPS) // Line and plane don't intersect - return(false); - mu = - (d + n.x * p1.x + n.y * p1.y + n.z * p1.z) / denom; - if (mu < 0 || mu > 1) // Intersection not along line segment - return(false); - p->x = p1.x + mu * (p2.x - p1.x); - p->y = p1.y + mu * (p2.y - p1.y); - p->z = p1.z + mu * (p2.z - p1.z); - - -/* //old method, slow as hell due to acos(), but it works well - - float a1,a2,a3; - float total; - VERTEX pa1,pa2,pa3; - - pa1.x = pa.x - p->x; - pa1.y = pa.y - p->y; - pa1.z = pa.z - p->z; - Normalize(&pa1); - pa2.x = pb.x - p->x; - pa2.y = pb.y - p->y; - pa2.z = pb.z - p->z; - Normalize(&pa2); - pa3.x = pc.x - p->x; - pa3.y = pc.y - p->y; - pa3.z = pc.z - p->z; - Normalize(&pa3); - a1 = pa1.x*pa2.x + pa1.y*pa2.y + pa1.z*pa2.z; - a2 = pa2.x*pa3.x + pa2.y*pa3.y + pa2.z*pa3.z; - a3 = pa3.x*pa1.x + pa3.y*pa1.y + pa3.z*pa1.z; - -//holy hell these 3 acos are slow, we need to rewrite this... -// total = (acos(a1) + acos(a2) + acos(a3)); -// if (ABS(total - 2*M_PI) > EPS) - total = (acos(a1) + acos(a2) + acos(a3)) * 57.2957795; - if (ABS(total - 360) > EPS) - return(false); - - return(true); -*/ - -/* - //yet another failed method, project triangle and point into - //2 space based on largest component of the normal - //and check the triangle there. - float tx, ty, tz; - if(n.x < 0) - tx = -n.x; - else - tx = n.x; - if(n.y < 0) - ty = -n.y; - else - ty = n.y; - if(n.z < 0) - tz = -n.z; - else - tz = n.z; - - VERTEX pa2, pb2, pc2; - if(tx < ty) { - //keep x - if(tz < ty) { - //keep z, drop y - pa2.x = pa.x; pa2.y = pa.z; - pb2.x = pb.x; pb2.y = pb.z; - pc2.x = pc.x; pc2.y = pc.z; - } else { - //keep y, drop z... - pa2.x = pa.x; pa2.y = pa.y; - pb2.x = pb.x; pb2.y = pb.y; - pc2.x = pc.x; pc2.y = pc.y; - } - } else { - //keep y - if(tz < tx) { - //keep z, drop x - pa2.x = pa.x; pa2.y = pa.z; - pb2.x = pb.x; pb2.y = pb.z; - pc2.x = pc.x; pc2.y = pc.z; - } else { - //keep y, drop z... - pa2.x = pa.x; pa2.y = pa.y; - pb2.x = pb.x; pb2.y = pb.y; - pc2.x = pc.x; pc2.y = pc.y; - } - } - - // Determine whether or not the intersection point is bounded by pa,pb,pc -#define Sign(p1, p2, p3) \ - ((p1->x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1->y - p3.y)) - bool b1, b2, b3; - - b1 = Sign(p, pa2, pb2) < 0.0f; - b2 = Sign(p, pb2, pc2) < 0.0f; - b3 = Sign(p, pc2, pa2) < 0.0f; - - return ((b1 == b2) && (b2 == b3)); -*/ - -/* //not working well, seems to block LOS a lot - - //a new check based on barycentric coordinates, stolen from - //http://www.flipcode.com/cgi-bin/fcmsg.cgi?thread_show=7766 - float xcp, ycp, xab, yab, xac, yac; - float divb; - VERTEX barycoords; - - xcp = p->x - pc.x; - ycp = p->z - pc.z; - if( xcp == 0.f && ycp == 0.f ) - return(true); - - xab = pb.x - pa.x; - yab = pb.z - pa.z; - divb = xab * ycp - yab * xcp; - if( divb == 0.f ) - return(false); - - xac = pc.x - pa.x; - yac = pc.z - pa.z; - - barycoords.y = ( -yac * xcp + xac * ycp ) / divb; - if( barycoords.y < -EPS || barycoords.y > (1+EPS) ) - return(false); // small error tolerance - if( barycoords.y < 0.f ) - barycoords.y = 0.f; - if( barycoords.y > 1.f ) - barycoords.y = 1.f; - -// barycoords.x = 1.f - barycoords.y; - - if( xcp != 0.f ) { - float div = -xac + barycoords.y * xab; - if( div == 0.f ) - return(false); // flat triangle - barycoords.z = 1.f - xcp / div ; - } else { - float div = -yac + barycoords.y * yab; - if( div == 0.f ) - return(false); // flat triangle - barycoords.z = 1.f - ycp / div ; - } - - if( barycoords.z < -EPS || barycoords.z > (1+EPS) ) - return(false); -// if( barycoords.z < 0.f ) -// barycoords.z = 0.f; -// if( barycoords.z > 1.f ) -// barycoords.z = 1.f; - -// barycoords.x *= 1.f - barycoords.z; -// barycoords.y *= 1.f - barycoords.z; - - return(true); -*/ - -/* - Yet another method adapted from this code: - Vec3 pa1 = pa - p; - Vec3 pa2 = pb - p; - float d = pa1.cross(pa2).dot(n); - if (d < 0) return false; - Vec3 pa3 = pb - p; - d = pa2.cross(pa3).dot(n); - if (d < 0) return false; - d = pa3.cross(pa1).dot(n); - if (d < 0) return false; - return true; -*/ - - //in practice, this seems to actually take longer - //than the arc cosine method above... - n.x = -n.x; - n.y = -n.y; - n.z = -n.z; - VERTEX pa1,pa2,pa3, tmp; - float t; - - //pa1 = pa - p - pa1.x = pa.x - p->x; - pa1.y = pa.y - p->y; - pa1.z = pa.z - p->z; - - //pa2 = pb - p - pa2.x = pb.x - p->x; - pa2.y = pb.y - p->y; - pa2.z = pb.z - p->z; - - //tmp = pa1 cross pa2 - tmp.x = pa1.y * pa2.z - pa1.z * pa2.y; - tmp.y = pa1.z * pa2.x - pa1.x * pa2.z; - tmp.z = pa1.x * pa2.y - pa1.y * pa2.x; - - //t = tmp dot n - t = tmp.x * n.x + tmp.y * n.y + tmp.z * n.z; - if(t < 0) - return(false); -//printf("t = %f\n", t); - - //pa3 = pb - p - pa3.x = pc.x - p->x; - pa3.y = pc.y - p->y; - pa3.z = pc.z - p->z; - - //tmp = pa2 cross pa3 - tmp.x = pa2.y * pa3.z - pa2.z * pa3.y; - tmp.y = pa2.z * pa3.x - pa2.x * pa3.z; - tmp.z = pa2.x * pa3.y - pa2.y * pa3.x; - - //t = tmp dot n - t = tmp.x * n.x + tmp.y * n.y + tmp.z * n.z; - if(t < 0) - return(false); -//printf("t = %f\n", t); - - //tmp = pa3 cross pa1 - tmp.x = pa3.y * pa1.z - pa3.z * pa1.y; - tmp.y = pa3.z * pa1.x - pa3.x * pa1.z; - tmp.z = pa3.x * pa1.y - pa3.y * pa1.x; - - //t = tmp dot n - t = tmp.x * n.x + tmp.y * n.y + tmp.z * n.z; - if(t < 0) - return(false); -//printf("t = %f\n", t); - - return(true); -} - -void Map::Normalize(VERTEX *p) { - float len = sqrtf(p->x*p->x + p->y*p->y + p->z*p->z); - p->x /= len; - p->y /= len; - p->z /= len; -} - -bool Map::LineIntersectsZoneNoZLeaps(VERTEX start, VERTEX end, float step_mag, VERTEX *result, FACE **on) { - float z = -999999; - VERTEX step; - VERTEX cur; - cur.x = start.x; - cur.y = start.y; - cur.z = start.z; - - step.x = end.x - start.x; - step.y = end.y - start.y; - step.z = end.z - start.z; - float factor = step_mag / sqrt(step.x*step.x + step.y*step.y + step.z*step.z); - - step.x *= factor; - step.y *= factor; - step.z *= factor; - - int steps = 0; - - if (step.x > 0 && step.x < 0.001f) - step.x = 0.001f; - if (step.y > 0 && step.y < 0.001f) - step.y = 0.001f; - if (step.z > 0 && step.z < 0.001f) - step.z = 0.001f; - if (step.x < 0 && step.x > -0.001f) - step.x = -0.001f; - if (step.y < 0 && step.y > -0.001f) - step.y = -0.001f; - if (step.z < 0 && step.z > -0.001f) - step.z = -0.001f; - - NodeRef cnode, lnode; - lnode = 0; - //while we are not past end - //always do this once, even if start == end. - while(cur.x != end.x || cur.y != end.y || cur.z != end.z) - { - steps++; - cnode = SeekNode(GetRoot(), cur.x, cur.y); - if (cnode == NODE_NONE) - { - return(true); - } - VERTEX me; - me.x = cur.x; - me.y = cur.y; - me.z = cur.z; - VERTEX hit; - FACE *onhit; - float best_z = zone->zonemap->FindBestZ(cnode, me, &hit, &onhit); - float diff = ABS(best_z-z); -// diff *= sign(diff); - if (z == -999999 || best_z == -999999 || diff < 12.0) - z = best_z; - else - return(true); - //look at current location - if(cnode != NODE_NONE && cnode != lnode) { - if(LineIntersectsNode(cnode, start, end, result, on)) - { - return(true); - } - lnode = cnode; - } - - //move 1 step - if (cur.x != end.x) - cur.x += step.x; - if (cur.y != end.y) - cur.y += step.y; - if (cur.z != end.z) - cur.z += step.z; - - //watch for end conditions - if ( (cur.x > end.x && end.x >= start.x) || (cur.x < end.x && end.x <= start.x) || (step.x == 0) ) { - cur.x = end.x; - } - if ( (cur.y > end.y && end.y >= start.y) || (cur.y < end.y && end.y <= start.y) || (step.y == 0) ) { - cur.y = end.y; - } - if ( (cur.z > end.z && end.z >= start.z) || (cur.z < end.z && end.z < start.z) || (step.z == 0) ) { - cur.z = end.z; - } - } - - //walked entire line and didnt run into anything... - return(false); -} - -float Map::FindClosestZ(VERTEX p ) const -{ - // Unlike FindBestZ, this method finds the closest Z value above or below the specified point. - // - std::list ZSet; - - NodeRef NodeR = SeekNode(MAP_ROOT_NODE, p.x, p.y); - - if( NodeR == NODE_NONE || NodeR >= m_Nodes) - return 0; - - PNODE CurrentNode = &mNodes[NodeR]; - - if(!(CurrentNode->flags & nodeFinal)) - return 0; - - VERTEX p1(p), p2(p), Result; - - p1.z = 999999; - - p2.z = BEST_Z_INVALID; - - const uint32 *CurrentFaceList = mFaceLists + CurrentNode->faces.offset; - - for(unsigned long i = 0; i < CurrentNode->faces.count; ++i) - { - if(*CurrentFaceList > m_Faces) - continue; - - PFACE CurrentFace = &mFinalFaces[ *CurrentFaceList ]; - - if(CurrentFace->nz > 0 && LineIntersectsFace(CurrentFace, p1, p2, &Result)) - ZSet.push_back(Result.z); - - CurrentFaceList++; - - } - if(ZSet.size() == 0) - return 0; - - if(ZSet.size() == 1) - return ZSet.front(); - - float ClosestZ = -999999; - - for(std::list::iterator Iterator = ZSet.begin(); Iterator != ZSet.end(); ++Iterator) - { - if(ABS(p.z - (*Iterator)) < ABS(p.z - ClosestZ)) - ClosestZ = (*Iterator); - } - - return ClosestZ; -} - diff --git a/zone/RaycastMesh.cpp b/zone/RaycastMesh.cpp new file mode 100644 index 000000000..b6635c7e6 --- /dev/null +++ b/zone/RaycastMesh.cpp @@ -0,0 +1,938 @@ +#include "RaycastMesh.h" +#include +#include +#include + +// This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do +// high-speed raycasting. +// +// There are much better implementations of this available on the internet. In particular I recommend that you use +// OPCODE written by Pierre Terdiman. +// @see: http://www.codercorner.com/Opcode.htm +// +// OPCODE does a whole lot more than just raycasting, and is a rather significant amount of source code. +// +// I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting. +// I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However, +// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders +// of magnitude; so this implementation should work fine for simple tools and utilities. +// +// It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees. +// AABB = Axis Aligned Bounding Volume trees. +// +// http://www.cgal.org/Manual/3.5/doc_html/cgal_manual/AABB_tree/Chapter_main.html +// +// +// This code snippet was written by John W. Ratcliff on August 18, 2011 and released under the MIT. license. +// +// mailto:jratcliffscarab@gmail.com +// +// The official source can be found at: http://code.google.com/p/raycastmesh/ +// +// + +#pragma warning(disable:4100) + +namespace RAYCAST_MESH +{ + +typedef std::vector< RmUint32 > TriVector; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** +* A method to compute a ray-AABB intersection. +* Original code by Andrew Woo, from "Graphics Gems", Academic Press, 1990 +* Optimized code by Pierre Terdiman, 2000 (~20-30% faster on my Celeron 500) +* Epsilon value added by Klaus Hartmann. (discarding it saves a few cycles only) +* +* Hence this version is faster as well as more robust than the original one. +* +* Should work provided: +* 1) the integer representation of 0.0f is 0x00000000 +* 2) the sign bit of the RmReal is the most significant one +* +* Report bugs: p.terdiman@codercorner.com +* +* \param aabb [in] the axis-aligned bounding box +* \param origin [in] ray origin +* \param dir [in] ray direction +* \param coord [out] impact coordinates +* \return true if ray intersects AABB +*/ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define RAYAABB_EPSILON 0.00001f +//! Integer representation of a RmRealing-point value. +#define IR(x) ((RmUint32&)x) + +bool intersectRayAABB(const RmReal MinB[3],const RmReal MaxB[3],const RmReal origin[3],const RmReal dir[3],RmReal coord[3]) +{ + bool Inside = true; + RmReal MaxT[3]; + MaxT[0]=MaxT[1]=MaxT[2]=-1.0f; + + // Find candidate planes. + for(RmUint32 i=0;i<3;i++) + { + if(origin[i] < MinB[i]) + { + coord[i] = MinB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir[i])) MaxT[i] = (MinB[i] - origin[i]) / dir[i]; + } + else if(origin[i] > MaxB[i]) + { + coord[i] = MaxB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir[i])) MaxT[i] = (MaxB[i] - origin[i]) / dir[i]; + } + } + + // Ray origin inside bounding box + if(Inside) + { + coord[0] = origin[0]; + coord[1] = origin[1]; + coord[2] = origin[2]; + return true; + } + + // Get largest of the maxT's for final choice of intersection + RmUint32 WhichPlane = 0; + if(MaxT[1] > MaxT[WhichPlane]) WhichPlane = 1; + if(MaxT[2] > MaxT[WhichPlane]) WhichPlane = 2; + + // Check final candidate actually inside box + if(IR(MaxT[WhichPlane])&0x80000000) return false; + + for(RmUint32 i=0;i<3;i++) + { + if(i!=WhichPlane) + { + coord[i] = origin[i] + MaxT[WhichPlane] * dir[i]; + #ifdef RAYAABB_EPSILON + if(coord[i] < MinB[i] - RAYAABB_EPSILON || coord[i] > MaxB[i] + RAYAABB_EPSILON) return false; + #else + if(coord[i] < MinB[i] || coord[i] > MaxB[i]) return false; + #endif + } + } + return true; // ray hits box +} + + + + +bool intersectLineSegmentAABB(const RmReal bmin[3],const RmReal bmax[3],const RmReal p1[3],const RmReal dir[3],RmReal &dist,RmReal intersect[3]) +{ + bool ret = false; + + if ( dist > RAYAABB_EPSILON ) + { + ret = intersectRayAABB(bmin,bmax,p1,dir,intersect); + if ( ret ) + { + RmReal dx = p1[0]-intersect[0]; + RmReal dy = p1[1]-intersect[1]; + RmReal dz = p1[2]-intersect[2]; + RmReal d = dx*dx+dy*dy+dz*dz; + if ( d < dist*dist ) + { + dist = sqrtf(d); + } + else + { + ret = false; + } + } + } + return ret; +} + +/* a = b - c */ +#define vector(a,b,c) \ + (a)[0] = (b)[0] - (c)[0]; \ + (a)[1] = (b)[1] - (c)[1]; \ + (a)[2] = (b)[2] - (c)[2]; + +#define innerProduct(v,q) \ + ((v)[0] * (q)[0] + \ + (v)[1] * (q)[1] + \ + (v)[2] * (q)[2]) + +#define crossProduct(a,b,c) \ + (a)[0] = (b)[1] * (c)[2] - (c)[1] * (b)[2]; \ + (a)[1] = (b)[2] * (c)[0] - (c)[2] * (b)[0]; \ + (a)[2] = (b)[0] * (c)[1] - (c)[0] * (b)[1]; + + +static inline bool rayIntersectsTriangle(const RmReal *p,const RmReal *d,const RmReal *v0,const RmReal *v1,const RmReal *v2,RmReal &t) +{ + RmReal e1[3],e2[3],h[3],s[3],q[3]; + RmReal a,f,u,v; + + vector(e1,v1,v0); + vector(e2,v2,v0); + crossProduct(h,d,e2); + a = innerProduct(e1,h); + + if (a > -0.00001 && a < 0.00001) + return(false); + + f = 1/a; + vector(s,p,v0); + u = f * (innerProduct(s,h)); + + if (u < 0.0 || u > 1.0) + return(false); + + crossProduct(q,s,e1); + v = f * innerProduct(d,q); + if (v < 0.0 || u + v > 1.0) + return(false); + // at this stage we can compute t to find out where + // the intersection point is on the line + t = f * innerProduct(e2,q); + if (t > 0) // ray intersection + return(true); + else // this means that there is a line intersection + // but not a ray intersection + return (false); +} + +static RmReal computePlane(const RmReal *A,const RmReal *B,const RmReal *C,RmReal *n) // returns D +{ + RmReal vx = (B[0] - C[0]); + RmReal vy = (B[1] - C[1]); + RmReal vz = (B[2] - C[2]); + + RmReal wx = (A[0] - B[0]); + RmReal wy = (A[1] - B[1]); + RmReal wz = (A[2] - B[2]); + + RmReal vw_x = vy * wz - vz * wy; + RmReal vw_y = vz * wx - vx * wz; + RmReal vw_z = vx * wy - vy * wx; + + RmReal mag = sqrt((vw_x * vw_x) + (vw_y * vw_y) + (vw_z * vw_z)); + + if ( mag < 0.000001f ) + { + mag = 0; + } + else + { + mag = 1.0f/mag; + } + + RmReal x = vw_x * mag; + RmReal y = vw_y * mag; + RmReal z = vw_z * mag; + + + RmReal D = 0.0f - ((x*A[0])+(y*A[1])+(z*A[2])); + + n[0] = x; + n[1] = y; + n[2] = z; + + return D; +} + + +#define TRI_EOF 0xFFFFFFFF + +enum AxisAABB +{ + AABB_XAXIS, + AABB_YAXIS, + AABB_ZAXIS +}; + +enum ClipCode +{ + CC_MINX = (1<<0), + CC_MAXX = (1<<1), + CC_MINY = (1<<2), + CC_MAXY = (1<<3), + CC_MINZ = (1<<4), + CC_MAXZ = (1<<5), +}; + + +class BoundsAABB +{ +public: + + + void setMin(const RmReal *v) + { + mMin[0] = v[0]; + mMin[1] = v[1]; + mMin[2] = v[2]; + } + + void setMax(const RmReal *v) + { + mMax[0] = v[0]; + mMax[1] = v[1]; + mMax[2] = v[2]; + } + + void setMin(RmReal x,RmReal y,RmReal z) + { + mMin[0] = x; + mMin[1] = y; + mMin[2] = z; + } + + void setMax(RmReal x,RmReal y,RmReal z) + { + mMax[0] = x; + mMax[1] = y; + mMax[2] = z; + } + + void include(const RmReal *v) + { + if ( v[0] < mMin[0] ) mMin[0] = v[0]; + if ( v[1] < mMin[1] ) mMin[1] = v[1]; + if ( v[2] < mMin[2] ) mMin[2] = v[2]; + + if ( v[0] > mMax[0] ) mMax[0] = v[0]; + if ( v[1] > mMax[1] ) mMax[1] = v[1]; + if ( v[2] > mMax[2] ) mMax[2] = v[2]; + } + + void getCenter(RmReal *center) const + { + center[0] = (mMin[0]+mMax[0])*0.5f; + center[1] = (mMin[1]+mMax[1])*0.5f; + center[2] = (mMin[2]+mMax[2])*0.5f; + } + + bool intersects(const BoundsAABB &b) const + { + if ((mMin[0] > b.mMax[0]) || (b.mMin[0] > mMax[0])) return false; + if ((mMin[1] > b.mMax[1]) || (b.mMin[1] > mMax[1])) return false; + if ((mMin[2] > b.mMax[2]) || (b.mMin[2] > mMax[2])) return false; + return true; + } + + bool containsTriangle(const RmReal *p1,const RmReal *p2,const RmReal *p3) const + { + BoundsAABB b; + b.setMin(p1); + b.setMax(p1); + b.include(p2); + b.include(p3); + return intersects(b); + } + + bool containsTriangleExact(const RmReal *p1,const RmReal *p2,const RmReal *p3,RmUint32 &orCode) const + { + bool ret = false; + + RmUint32 andCode; + orCode = getClipCode(p1,p2,p3,andCode); + if ( andCode == 0 ) + { + ret = true; + } + + return ret; + } + + inline RmUint32 getClipCode(const RmReal *p1,const RmReal *p2,const RmReal *p3,RmUint32 &andCode) const + { + andCode = 0xFFFFFFFF; + RmUint32 c1 = getClipCode(p1); + RmUint32 c2 = getClipCode(p2); + RmUint32 c3 = getClipCode(p3); + andCode&=c1; + andCode&=c2; + andCode&=c3; + return c1|c2|c3; + } + + inline RmUint32 getClipCode(const RmReal *p) const + { + RmUint32 ret = 0; + + if ( p[0] < mMin[0] ) + { + ret|=CC_MINX; + } + else if ( p[0] > mMax[0] ) + { + ret|=CC_MAXX; + } + + if ( p[1] < mMin[1] ) + { + ret|=CC_MINY; + } + else if ( p[1] > mMax[1] ) + { + ret|=CC_MAXY; + } + + if ( p[2] < mMin[2] ) + { + ret|=CC_MINZ; + } + else if ( p[2] > mMax[2] ) + { + ret|=CC_MAXZ; + } + + return ret; + } + + inline void clamp(const BoundsAABB &aabb) + { + if ( mMin[0] < aabb.mMin[0] ) mMin[0] = aabb.mMin[0]; + if ( mMin[1] < aabb.mMin[1] ) mMin[1] = aabb.mMin[1]; + if ( mMin[2] < aabb.mMin[2] ) mMin[2] = aabb.mMin[2]; + if ( mMax[0] > aabb.mMax[0] ) mMax[0] = aabb.mMax[0]; + if ( mMax[1] > aabb.mMax[1] ) mMax[1] = aabb.mMax[1]; + if ( mMax[2] > aabb.mMax[2] ) mMax[2] = aabb.mMax[2]; + } + + RmReal mMin[3]; + RmReal mMax[3]; +}; + + +class NodeAABB; + +class NodeInterface +{ +public: + virtual NodeAABB * getNode(void) = 0; + virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) = 0; +}; + + + + + + class NodeAABB + { + public: + NodeAABB(void) + { + mLeft = NULL; + mRight = NULL; + mLeafTriangleIndex= TRI_EOF; + } + + NodeAABB(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,RmUint32 *indices, + RmUint32 maxDepth, // Maximum recursion depth for the triangle mesh. + RmUint32 minLeafSize, // minimum triangles to treat as a 'leaf' node. + RmReal minAxisSize, + NodeInterface *callback, + TriVector &leafTriangles) // once a particular axis is less than this size, stop sub-dividing. + + { + mLeft = NULL; + mRight = NULL; + mLeafTriangleIndex = TRI_EOF; + TriVector triangles; + triangles.reserve(tcount); + for (RmUint32 i=0; i dx ) + { + axis = AABB_YAXIS; + laxis = dy; + } + + if ( dz > dx && dz > dy ) + { + axis = AABB_ZAXIS; + laxis = dz; + } + + RmUint32 count = triangles.size(); + + // if the number of triangles is less than the minimum allowed for a leaf node or... + // we have reached the maximum recursion depth or.. + // the width of the longest axis is less than the minimum axis size then... + // we create the leaf node and copy the triangles into the leaf node triangle array. + if ( count < minLeafSize || depth >= maxDepth || laxis < minAxisSize ) + { + // Copy the triangle indices into the leaf triangles array + mLeafTriangleIndex = leafTriangles.size(); // assign the array start location for these leaf triangles. + leafTriangles.push_back(count); + for (TriVector::const_iterator i=triangles.begin(); i!=triangles.end(); ++i) + { + RmUint32 tri = *i; + leafTriangles.push_back(tri); + } + } + else + { + RmReal center[3]; + mBounds.getCenter(center); + BoundsAABB b1,b2; + splitRect(axis,mBounds,b1,b2,center); + + // Compute two bounding boxes based upon the split of the longest axis + + BoundsAABB leftBounds,rightBounds; + + TriVector leftTriangles; + TriVector rightTriangles; + + + // Create two arrays; one of all triangles which intersect the 'left' half of the bounding volume node + // and another array that includes all triangles which intersect the 'right' half of the bounding volume node. + for (TriVector::const_iterator i=triangles.begin(); i!=triangles.end(); ++i) + { + + RmUint32 tri = (*i); + + { + RmUint32 i1 = indices[tri*3+0]; + RmUint32 i2 = indices[tri*3+1]; + RmUint32 i3 = indices[tri*3+2]; + + const RmReal *p1 = &vertices[i1*3]; + const RmReal *p2 = &vertices[i2*3]; + const RmReal *p3 = &vertices[i3*3]; + + RmUint32 addCount = 0; + RmUint32 orCode=0xFFFFFFFF; + if ( b1.containsTriangleExact(p1,p2,p3,orCode)) + { + addCount++; + if ( leftTriangles.empty() ) + { + leftBounds.setMin(p1); + leftBounds.setMax(p1); + } + leftBounds.include(p1); + leftBounds.include(p2); + leftBounds.include(p3); + leftTriangles.push_back(tri); // Add this triangle to the 'left triangles' array and revise the left triangles bounding volume + } + // if the orCode is zero; meaning the triangle was fully self-contiained int he left bounding box; then we don't need to test against the right + if ( orCode && b2.containsTriangleExact(p1,p2,p3,orCode)) + { + addCount++; + if ( rightTriangles.empty() ) + { + rightBounds.setMin(p1); + rightBounds.setMax(p1); + } + rightBounds.include(p1); + rightBounds.include(p2); + rightBounds.include(p3); + rightTriangles.push_back(tri); // Add this triangle to the 'right triangles' array and revise the right triangles bounding volume. + } + assert( addCount ); + } + } + + if ( !leftTriangles.empty() ) // If there are triangles in the left half then... + { + leftBounds.clamp(b1); // we have to clamp the bounding volume so it stays inside the parent volume. + mLeft = callback->getNode(); // get a new AABB node + new ( mLeft ) NodeAABB(leftBounds); // initialize it to default constructor values. + // Then recursively split this node. + mLeft->split(leftTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles); + } + + if ( !rightTriangles.empty() ) // If there are triangles in the right half then.. + { + rightBounds.clamp(b2); // clamps the bounding volume so it stays restricted to the size of the parent volume. + mRight = callback->getNode(); // allocate and default initialize a new node + new ( mRight ) NodeAABB(rightBounds); + // Recursively split this node. + mRight->split(rightTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles); + } + + } + } + + void splitRect(AxisAABB axis,const BoundsAABB &source,BoundsAABB &b1,BoundsAABB &b2,const RmReal *midpoint) + { + switch ( axis ) + { + case AABB_XAXIS: + { + b1.setMin( source.mMin ); + b1.setMax( midpoint[0], source.mMax[1], source.mMax[2] ); + + b2.setMin( midpoint[0], source.mMin[1], source.mMin[2] ); + b2.setMax(source.mMax); + } + break; + case AABB_YAXIS: + { + b1.setMin(source.mMin); + b1.setMax(source.mMax[0], midpoint[1], source.mMax[2]); + + b2.setMin(source.mMin[0], midpoint[1], source.mMin[2]); + b2.setMax(source.mMax); + } + break; + case AABB_ZAXIS: + { + b1.setMin(source.mMin); + b1.setMax(source.mMax[0], source.mMax[1], midpoint[2]); + + b2.setMin(source.mMin[0], source.mMin[1], midpoint[2]); + b2.setMax(source.mMax); + } + break; + } + } + + + virtual void raycast(bool &hit, + const RmReal *from, + const RmReal *to, + const RmReal *dir, + RmReal *hitLocation, + RmReal *hitNormal, + RmReal *hitDistance, + const RmReal *vertices, + const RmUint32 *indices, + RmReal &nearestDistance, + NodeInterface *callback, + RmUint32 *raycastTriangles, + RmUint32 raycastFrame, + const TriVector &leafTriangles, + RmUint32 &nearestTriIndex) + { + RmReal sect[3]; + RmReal nd = nearestDistance; + if ( !intersectLineSegmentAABB(mBounds.mMin,mBounds.mMax,from,dir,nd,sect) ) + { + return; + } + if ( mLeafTriangleIndex != TRI_EOF ) + { + const RmUint32 *scan = &leafTriangles[mLeafTriangleIndex]; + RmUint32 count = *scan++; + for (RmUint32 i=0; igetFaceNormal(tri,hitNormal); + } + if ( hitDistance ) + { + *hitDistance = t; + } + nearestTriIndex = tri; + hit = true; + } + } + } + } + } + else + { + if ( mLeft ) + { + mLeft->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,vertices,indices,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex); + } + if ( mRight ) + { + mRight->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,vertices,indices,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex); + } + } + } + + NodeAABB *mLeft; // left node + NodeAABB *mRight; // right node + BoundsAABB mBounds; // bounding volume of node + RmUint32 mLeafTriangleIndex; // if it is a leaf node; then these are the triangle indices. + }; + +class MyRaycastMesh : public RaycastMesh, public NodeInterface +{ +public: + + MyRaycastMesh(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,const RmUint32 *indices,RmUint32 maxDepth,RmUint32 minLeafSize,RmReal minAxisSize) + { + mRaycastFrame = 0; + if ( maxDepth < 2 ) + { + maxDepth = 2; + } + if ( maxDepth > 15 ) + { + maxDepth = 15; + } + RmUint32 pow2Table[16] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536 }; + mMaxNodeCount = 0; + for (RmUint32 i=0; i<=maxDepth; i++) + { + mMaxNodeCount+=pow2Table[i]; + } + mNodes = new NodeAABB[mMaxNodeCount]; + mNodeCount = 0; + mVcount = vcount; + mVertices = (RmReal *)::malloc(sizeof(RmReal)*3*vcount); + memcpy(mVertices,vertices,sizeof(RmReal)*3*vcount); + mTcount = tcount; + mIndices = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount*3); + memcpy(mIndices,indices,sizeof(RmUint32)*tcount*3); + mRaycastTriangles = (RmUint32 *)::malloc(tcount*sizeof(RmUint32)); + memset(mRaycastTriangles,0,tcount*sizeof(RmUint32)); + mRoot = getNode(); + mFaceNormals = NULL; + new ( mRoot ) NodeAABB(mVcount,mVertices,mTcount,mIndices,maxDepth,minLeafSize,minAxisSize,this,mLeafTriangles); + } + + ~MyRaycastMesh(void) + { + delete []mNodes; + ::free(mVertices); + ::free(mIndices); + ::free(mFaceNormals); + ::free(mRaycastTriangles); + } + + virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance) + { + bool ret = false; + + RmReal dir[3]; + dir[0] = to[0] - from[0]; + dir[1] = to[1] - from[1]; + dir[2] = to[2] - from[2]; + RmReal distance = sqrtf( dir[0]*dir[0] + dir[1]*dir[1]+dir[2]*dir[2] ); + if ( distance < 0.0000000001f ) return false; + RmReal recipDistance = 1.0f / distance; + dir[0]*=recipDistance; + dir[1]*=recipDistance; + dir[2]*=recipDistance; + mRaycastFrame++; + RmUint32 nearestTriIndex=TRI_EOF; + mRoot->raycast(ret,from,to,dir,hitLocation,hitNormal,hitDistance,mVertices,mIndices,distance,this,mRaycastTriangles,mRaycastFrame,mLeafTriangles,nearestTriIndex); + return ret; + } + + virtual void release(void) + { + delete this; + } + + virtual const RmReal * getBoundMin(void) const // return the minimum bounding box + { + return mRoot->mBounds.mMin; + } + virtual const RmReal * getBoundMax(void) const // return the maximum bounding box. + { + return mRoot->mBounds.mMax; + } + + virtual NodeAABB * getNode(void) + { + assert( mNodeCount < mMaxNodeCount ); + NodeAABB *ret = &mNodes[mNodeCount]; + mNodeCount++; + return ret; + } + + virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) + { + if ( mFaceNormals == NULL ) + { + mFaceNormals = (RmReal *)::malloc(sizeof(RmReal)*3*mTcount); + for (RmUint32 i=0; i(m); +} + + diff --git a/zone/RaycastMesh.h b/zone/RaycastMesh.h new file mode 100644 index 000000000..eec2e0ba4 --- /dev/null +++ b/zone/RaycastMesh.h @@ -0,0 +1,60 @@ +#ifndef RAYCAST_MESH_H + +#define RAYCAST_MESH_H + +// This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do +// high-speed raycasting. +// +// There are much better implementations of this available on the internet. In particular I recommend that you use +// OPCODE written by Pierre Terdiman. +// @see: http://www.codercorner.com/Opcode.htm +// +// OPCODE does a whole lot more than just raycasting, and is a rather significant amount of source code. +// +// I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting. +// I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However, +// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders +// of magnitude; so this implementation should work fine for simple tools and utilities. +// +// It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees. +// AABB = Axis Aligned Bounding Volume trees. +// +// http://www.cgal.org/Manual/3.5/doc_html/cgal_manual/AABB_tree/Chapter_main.html +// +// +// This code snippet was written by John W. Ratcliff on August 18, 2011 and released under the MIT. license. +// +// mailto:jratcliffscarab@gmail.com +// +// The official source can be found at: http://code.google.com/p/raycastmesh/ +// +// + +typedef float RmReal; +typedef unsigned int RmUint32; + +class RaycastMesh +{ +public: + virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance) = 0; + virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance) = 0; + + virtual const RmReal * getBoundMin(void) const = 0; // return the minimum bounding box + virtual const RmReal * getBoundMax(void) const = 0; // return the maximum bounding box. + virtual void release(void) = 0; +protected: + virtual ~RaycastMesh(void) { }; +}; + + +RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in the source triangle mesh + const RmReal *vertices, // The array of vertex positions in the format x1,y1,z1..x2,y2,z2.. etc. + RmUint32 tcount, // The number of triangles in the source triangle mesh + const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ... + RmUint32 maxDepth=15, // Maximum recursion depth for the triangle mesh. + RmUint32 minLeafSize=4, // minimum triangles to treat as a 'leaf' node. + RmReal minAxisSize=0.01f // once a particular axis is less than this size, stop sub-dividing. + ); + + +#endif \ No newline at end of file diff --git a/zone/map.h b/zone/map.h deleted file mode 100644 index 9ab453ff6..000000000 --- a/zone/map.h +++ /dev/null @@ -1,190 +0,0 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) - - 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 -*/ -#ifndef MAP_H -#define MAP_H - -#include - -//this is the current version number to expect from the map header -#define MAP_VERSION 0x01000000 - -#define BEST_Z_INVALID -999999 - -#pragma pack(1) - -typedef struct _vertex{ -// unsigned long order; - float x; - float y; - float z; - - _vertex() - { - x = y = z = 0.0f; - }; - - _vertex(float ix, float iy, float iz) - { - x = ix; - y = iy; - z = iz; - } - bool operator==(const _vertex &v1) const - { - return((v1.x == x) && (v1.y == y) && (v1.z ==z)); - } - -}VERTEX, *PVERTEX; - -typedef struct _face{ -// unsigned long a, b, c; //vertexs - VERTEX a; - VERTEX b; - VERTEX c; - float nx, ny, nz, nd; -}FACE, *PFACE; - -typedef struct _mapHeader { - uint32 version; - uint32 face_count; - uint16 node_count; - uint32 facelist_count; -} mapHeader; - -/* - This is used for the recursive node structure - unsigned shorts are adequate because, worst case - even in a zone that is 6000x6000 with a small node - size of 30x30, there are only 40000 nodes. - - quadrent definitions: - quad 1 (nodes[0]): - x>=0, y>=0 - quad 2 (nodes[1]): - x<0, y>=0 - quad 3 (nodes[2]): - x<0, y<0 - quad 4 (nodes[3]): - x>=0, y<0 - - */ -enum { //node flags - nodeFinal = 0x01 - //7 more bits if theres something to use them for... -}; -typedef struct _nodeHeader { - //bounding box of this node - //there is no reason that these could not be unsigned - //shorts other than we have to compare them to floats - //all over the place, so they stay floats for now. - //changing it could save 8 bytes per node record (~320k for huge maps) - float minx; - float miny; - float maxx; - float maxy; - - uint8 flags; - union { - uint16 nodes[4]; //index 0 means nullptr, not root - struct { - uint32 count; - uint32 offset; - } faces; - }; -} nodeHeader, NODE, *PNODE; - -#pragma pack() - -//special value returned as 'not found' -#define NODE_NONE 65534 -#define MAP_ROOT_NODE 0 - -typedef uint16 NodeRef; - -/*typedef struct _node { - nodeHeader head; - unsigned int * pfaces; - char mask; - struct _node * node1, *node2, *node3, *node4; -}NODE, *PNODE;*/ - -class Map { -public: - static Map* LoadMapfile(const char* in_zonename, const char *directory = nullptr); - - Map(); - ~Map(); - - bool loadMap(FILE *fp); - - //the result is always final, except special NODE_NONE - NodeRef SeekNode( NodeRef _node, float x, float y ) const; - - //these are untested since rewrite: - int *SeekFace( NodeRef _node, float x, float y ); - float GetFaceHeight( int _idx, float x, float y ) const; - - bool LocWithinNode( NodeRef _node, float x, float y ) const; - - //nodes to these functions must be final - bool LineIntersectsNode( NodeRef _node, VERTEX start, VERTEX end, VERTEX *result, FACE **on = nullptr) const; - bool LineIntersectsFace( PFACE cface, VERTEX start, VERTEX end, VERTEX *result) const; - float FindBestZ( NodeRef _node, VERTEX start, VERTEX *result, FACE **on = nullptr) const; - bool LineIntersectsZone(VERTEX start, VERTEX end, float step, VERTEX *result, FACE **on = nullptr) const; - -// inline unsigned int GetVertexNumber( ) {return m_Vertex; } - inline uint32 GetFacesNumber( ) const { return m_Faces; } -// inline PVERTEX GetVertex( int _idx ) {return mFinalVertex + _idx; } - inline PFACE GetFace( int _idx) {return mFinalFaces + _idx; } - inline PFACE GetFaceFromlist( int _idx) {return &mFinalFaces[ mFaceLists[_idx] ]; } - inline NodeRef GetRoot( ) const { return MAP_ROOT_NODE; } - inline PNODE GetNode( NodeRef r ) { return( mNodes + r ); } - - inline float GetMinX() const { return(_minx); } - inline float GetMaxX() const { return(_maxx); } - inline float GetMinY() const { return(_miny); } - inline float GetMaxY() const { return(_maxy); } - inline float GetMinZ() const { return(_minz); } - inline float GetMaxZ() const { return(_maxz); } - bool LineIntersectsZoneNoZLeaps(VERTEX start, VERTEX end, float step_mag, VERTEX *result, FACE **on); - float FindClosestZ(VERTEX p ) const; - -private: -// unsigned long m_Vertex; - uint32 m_Faces; - uint32 m_Nodes; - uint32 m_FaceLists; -// PVERTEX mFinalVertex; - PFACE mFinalFaces; - PNODE mNodes; - uint32 *mFaceLists; - - - int mCandFaces[100]; - - float _minz, _maxz; - float _minx, _miny, _maxx, _maxy; - - static void Normalize(VERTEX *p); - -// void RecLoadNode( PNODE _node, FILE *l_f ); -// void RecFreeNode( PNODE _node ); -}; - -#endif - From 951c321ba6289cc906c02fe43a46831d8ca8d552 Mon Sep 17 00:00:00 2001 From: KimLS Date: Sun, 23 Feb 2014 21:17:28 -0800 Subject: [PATCH 02/37] Partial port of der's map rewrite, no new azone format yet but it's coming. Will convert legacy map formats on the fly atm. --- zone/CMakeLists.txt | 2 +- zone/MobAI.cpp | 12 +- zone/aggro.cpp | 198 +------------------ zone/client.h | 4 +- zone/client_packet.cpp | 47 ++--- zone/command.cpp | 40 ++-- zone/entity.cpp | 14 +- zone/fearpath.cpp | 427 +---------------------------------------- zone/forage.cpp | 30 ++- zone/map.cpp | 268 ++++++++++++++++++++++++++ zone/map.h | 67 +++++++ zone/mob.cpp | 44 ++--- zone/mob.h | 9 +- zone/pathing.cpp | 153 +++++++-------- zone/pathing.h | 14 +- zone/waypoints.cpp | 28 +-- zone/zone.cpp | 2 +- 17 files changed, 517 insertions(+), 842 deletions(-) create mode 100644 zone/map.cpp create mode 100644 zone/map.h diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 68c4678f3..dec667f40 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -66,7 +66,7 @@ SET(zone_sources horse.cpp inventory.cpp loottables.cpp - Map.cpp + map.cpp merc.cpp mob.cpp MobAI.cpp diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index 38addf695..dbb336b92 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -754,7 +754,7 @@ void Client::AI_Process() { bool WaypointChanged, NodeReached; - VERTEX Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z, + Map::Vertex Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z, GetFearSpeed(), WaypointChanged, NodeReached); if(WaypointChanged) @@ -910,7 +910,7 @@ void Client::AI_Process() else { bool WaypointChanged, NodeReached; - VERTEX Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), + Map::Vertex Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetRunspeed(), WaypointChanged, NodeReached); if(WaypointChanged) @@ -1022,7 +1022,7 @@ void Mob::AI_Process() { { bool WaypointChanged, NodeReached; - VERTEX Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z, + Map::Vertex Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z, GetFearSpeed(), WaypointChanged, NodeReached); if(WaypointChanged) @@ -1360,7 +1360,7 @@ void Mob::AI_Process() { { bool WaypointChanged, NodeReached; - VERTEX Goal = UpdatePath(target->GetX(), target->GetY(), target->GetZ(), + Map::Vertex Goal = UpdatePath(target->GetX(), target->GetY(), target->GetZ(), GetRunspeed(), WaypointChanged, NodeReached); if(WaypointChanged) @@ -1683,7 +1683,7 @@ void NPC::AI_DoMovement() { { bool WaypointChanged; bool NodeReached; - VERTEX Goal = UpdatePath(cur_wp_x, cur_wp_y, cur_wp_z, walksp, WaypointChanged, NodeReached); + Map::Vertex Goal = UpdatePath(cur_wp_x, cur_wp_y, cur_wp_z, walksp, WaypointChanged, NodeReached); if(WaypointChanged) tar_ndx = 20; @@ -1722,7 +1722,7 @@ void NPC::AI_DoMovement() { if(!((x_pos == guard_x) && (y_pos == guard_y) && (z_pos == guard_z))) { bool WaypointChanged, NodeReached; - VERTEX Goal = UpdatePath(guard_x, guard_y, guard_z, walksp, WaypointChanged, NodeReached); + Map::Vertex Goal = UpdatePath(guard_x, guard_y, guard_z, walksp, WaypointChanged, NodeReached); if(WaypointChanged) tar_ndx = 20; diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 326e320c0..436ad8586 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -872,133 +872,6 @@ bool Mob::CombatRange(Mob* other) return false; } -//Old LOS function, prolly not used anymore -//Not removed because I havent looked it over to see if anything -//useful is in here before we delete it. -bool Mob::CheckLos(Mob* other) { - if (zone->zonemap == 0) - { - return true; - } - float tmp_x = GetX(); - float tmp_y = GetY(); - float tmp_z = GetZ(); - float trg_x = other->GetX(); - float trg_y = other->GetY(); - float trg_z = other->GetZ(); - float perwalk_x = 0.5; - float perwalk_y = 0.5; - float perwalk_z = 0.5; - float dist_x = tmp_x - trg_x; - if (dist_x < 0) - dist_x *= -1; - float dist_y = tmp_y - trg_y; - if (dist_y < 0) - dist_y *= -1; - float dist_z = tmp_z - trg_z; - if (dist_z < 0) - dist_z *= -1; - if (dist_x < dist_y && dist_z < dist_y) - { - perwalk_x /= (dist_y/dist_x); - perwalk_z /= (dist_y/dist_z); - } - else if (dist_y < dist_x && dist_z < dist_x) - { - perwalk_y /= (dist_x/dist_y); - perwalk_z /= (dist_x/dist_z); - } - else if (dist_x < dist_z && dist_y < dist_z) - { - perwalk_x /= (dist_z/dist_x); - perwalk_y /= (dist_z/dist_y); - } - float steps = (dist_x/perwalk_x + dist_y/perwalk_y + dist_z/perwalk_z)*10; //Just a safety check to prevent endless loops. - while (steps > 0) { - steps--; - if (tmp_x < trg_x) - { - if (tmp_x + perwalk_x < trg_x) - tmp_x += perwalk_x; - else - tmp_x = trg_x; - } - if (tmp_y < trg_y) - { - if (tmp_y + perwalk_y < trg_y) - tmp_y += perwalk_y; - else - tmp_y = trg_y; - } - if (tmp_z < trg_z) - { - if (tmp_z + perwalk_z < trg_z) - tmp_z += perwalk_z; - else - tmp_z = trg_z; - } - if (tmp_x > trg_x) - { - if (tmp_x - perwalk_x > trg_x) - tmp_x -= perwalk_x; - else - tmp_x = trg_x; - } - if (tmp_y > trg_y) - { - if (tmp_y - perwalk_y > trg_y) - tmp_y -= perwalk_y; - else - tmp_y = trg_y; - } - if (tmp_z > trg_z) - { - if (tmp_z - perwalk_z > trg_z) - tmp_z -= perwalk_z; - else - tmp_z = trg_z; - } - if (tmp_y == trg_y && tmp_x == trg_x && tmp_z == trg_z) - { - return true; - } - -//I believe this is contributing to breaking mob spawns when a map is loaded -// NodeRef pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), tmp_x, tmp_y ); - NodeRef pnode = NODE_NONE; - if (pnode != NODE_NONE) - { - const int *iface = zone->zonemap->SeekFace( pnode, tmp_x, tmp_y ); - if (*iface == -1) { - return false; - } - float temp_z = 0; - float best_z = 999999; - while(*iface != -1) - { - temp_z = zone->zonemap->GetFaceHeight( *iface, x_pos, y_pos ); -//UMM.. OMG... sqrtf(pow(x, 2)) == x.... retards - float best_dist = sqrtf((float)(pow(best_z-tmp_z, 2))); - float tmp_dist = sqrtf((float)(pow(tmp_z-tmp_z, 2))); - if (tmp_dist < best_dist) - { - best_z = temp_z; - } - iface++; - } -/* solar: our aggro code isn't using this right now, just spells, so i'm - taking out the +-10 check for now to make it work right on hills - if (best_z - 10 > trg_z || best_z + 10 < trg_z) - { - return false; - } -*/ - } - } - return true; -} - - //Father Nitwit's LOS code bool Mob::CheckLosFN(Mob* other) { bool Result = false; @@ -1020,8 +893,8 @@ bool Mob::CheckLosFN(float posX, float posY, float posZ, float mobSize) { #endif } - VERTEX myloc; - VERTEX oloc; + Map::Vertex myloc; + Map::Vertex oloc; #define LOS_DEFAULT_HEIGHT 6.0f @@ -1036,72 +909,7 @@ bool Mob::CheckLosFN(float posX, float posY, float posZ, float mobSize) { #if LOSDEBUG>=5 LogFile->write(EQEMuLog::Debug, "LOS from (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f) sizes: (%.2f, %.2f)", myloc.x, myloc.y, myloc.z, oloc.x, oloc.y, oloc.z, GetSize(), mobSize); #endif - - FACE *onhit; - NodeRef mynode; - NodeRef onode; - - VERTEX hit; - //see if anything in our node is in the way - mynode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), myloc.x, myloc.y); - if(mynode != NODE_NONE) { - if(zone->zonemap->LineIntersectsNode(mynode, myloc, oloc, &hit, &onhit)) { -#if LOSDEBUG>=5 - LogFile->write(EQEMuLog::Debug, "Check LOS for %s target position, cannot see.", GetName()); - LogFile->write(EQEMuLog::Debug, "\tPoly: (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)\n", - onhit->a.x, onhit->a.y, onhit->a.z, - onhit->b.x, onhit->b.y, onhit->b.z, - onhit->c.x, onhit->c.y, onhit->c.z); -#endif - return(false); - } - } -#if LOSDEBUG>=5 - else { - LogFile->write(EQEMuLog::Debug, "WTF, I have no node, what am I standing on??? (%.2f, %.2f).", myloc.x, myloc.y); - } -#endif - - //see if they are in a different node. - //if so, see if anything in their node is blocking me. - if(! zone->zonemap->LocWithinNode(mynode, oloc.x, oloc.y)) { - onode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), oloc.x, oloc.y); - if(onode != NODE_NONE && onode != mynode) { - if(zone->zonemap->LineIntersectsNode(onode, myloc, oloc, &hit, &onhit)) { -#if LOSDEBUG>=5 - LogFile->write(EQEMuLog::Debug, "Check LOS for %s target position, cannot see (2).", GetName()); - LogFile->write(EQEMuLog::Debug, "\tPoly: (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)\n", - onhit->a.x, onhit->a.y, onhit->a.z, - onhit->b.x, onhit->b.y, onhit->b.z, - onhit->c.x, onhit->c.y, onhit->c.z); -#endif - return(false); - } - } -#if LOSDEBUG>=5 - else if(onode == NODE_NONE) { - LogFile->write(EQEMuLog::Debug, "WTF, They have no node, what are they standing on??? (%.2f, %.2f).", myloc.x, myloc.y); - } -#endif - } - - /* - if(zone->zonemap->LineIntersectsZone(myloc, oloc, CHECK_LOS_STEP, &onhit)) { -#if LOSDEBUG>=5 - LogFile->write(EQEMuLog::Debug, "Check LOS for %s target %s, cannot see.", GetName(), other->GetName() ); - LogFile->write(EQEMuLog::Debug, "\tPoly: (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)\n", - onhit->a.x, onhit->a.y, onhit->a.z, - onhit->b.x, onhit->b.y, onhit->b.z, - onhit->c.x, onhit->c.y, onhit->c.z); -#endif - return(false); - }*/ - -#if LOSDEBUG>=5 - LogFile->write(EQEMuLog::Debug, "Check LOS for %s target position, CAN SEE.", GetName()); -#endif - - return(true); + return zone->zonemap->CheckLoS(myloc, oloc); } //offensive spell aggro diff --git a/zone/client.h b/zone/client.h index be1c29c1c..aa4b48dd8 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1200,8 +1200,8 @@ protected: Mob* bind_sight_target; - VERTEX aa_los_me; - VERTEX aa_los_them; + Map::Vertex aa_los_me; + Map::Vertex aa_los_them; Mob *aa_los_them_mob; bool los_status; float aa_los_me_heading; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index d304715e4..89443ce04 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -8550,10 +8550,10 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app) } else { - VERTEX Start(GetX(), GetY(), GetZ() + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION); - VERTEX End(target->GetX(), target->GetY(), target->GetZ() + (target->GetSize() < 6.0 ? 6 : target->GetSize()) * HEAD_POSITION); + Map::Vertex Start(GetX(), GetY(), GetZ() + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION); + Map::Vertex End(target->GetX(), target->GetY(), target->GetZ() + (target->GetSize() < 6.0 ? 6 : target->GetSize()) * HEAD_POSITION); - if(!zone->zonemap->LineIntersectsZone(Start, End, 1.0f, nullptr, nullptr) && zone->pathing->NoHazards(Start, End)) + if(!zone->zonemap->LineIntersectsZone(Start, End, 1.0f, nullptr) && zone->pathing->NoHazards(Start, End)) { points.resize(2); points[0].x = Start.x; @@ -8594,7 +8594,7 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app) bool LeadsToTeleporter = false; - VERTEX v = zone->pathing->GetPathNodeCoordinates(pathlist.back()); + Map::Vertex v = zone->pathing->GetPathNodeCoordinates(pathlist.back()); p.x = v.x; p.y = v.y; @@ -8614,7 +8614,7 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app) break; } - VERTEX v = zone->pathing->GetPathNodeCoordinates((*Iterator), false); + Map::Vertex v = zone->pathing->GetPathNodeCoordinates((*Iterator), false); p.x = v.x; p.y = v.y; p.z = v.z; @@ -8785,33 +8785,32 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) { //if we zone in with invalid Z, fix it. if (zone->zonemap != nullptr) { - //for whatever reason, LineIntersectsNode is giving better results than FindBestZ - - NodeRef pnode; - VERTEX me; + Map::Vertex me; me.x = GetX(); me.y = GetY(); - me.z = GetZ() + (GetSize()==0.0?6:GetSize()); - pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), me.x, me.y ); + me.z = GetZ() + (GetSize() == 0.0 ? 6 : GetSize()); - VERTEX hit; - VERTEX below_me(me); - below_me.z -= 500; - if(!zone->zonemap->LineIntersectsNode(pnode, me, below_me, &hit, nullptr) || hit.z < -5000) { + Map::Vertex hit; + + if (zone->zonemap->FindBestZ(me, &hit) == BEST_Z_INVALID) + { #if EQDEBUG >= 5 LogFile->write(EQEMuLog::Debug, "Player %s started below the zone trying to fix! (%.3f, %.3f, %.3f)", GetName(), me.x, me.y, me.z); #endif - //theres nothing below us... try to find something to stand on me.z += 200; //arbitrary # - if(zone->zonemap->LineIntersectsNode(pnode, me, below_me, &hit, nullptr)) { + if (zone->zonemap->FindBestZ(me, &hit) != BEST_Z_INVALID) + { //+10 so they dont stick in the ground SendTo(me.x, me.y, hit.z + 10); m_pp.z = hit.z + 10; - } else { + } + else + { //one more, desperate try me.z += 2000; - if(zone->zonemap->LineIntersectsNode(pnode, me, below_me, &hit, nullptr)) { - //+10 so they dont stick in the ground + if (zone->zonemap->FindBestZ(me, &hit) != BEST_Z_INVALID) + { + //+10 so they dont stick in the ground SendTo(me.x, me.y, hit.z + 10); m_pp.z = hit.z + 10; } @@ -8819,14 +8818,6 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) { } } - //m_pp.hunger_level = 6000; - //m_pp.thirst_level = 6000; - - //aa_title = m_pp.aa_title; - //m_pp.timeplayed=64; - //m_pp.birthday=1057434792; - //m_pp.lastlogin=1057464792; - if (m_pp.gm && admin < minStatusToBeGM) m_pp.gm = 0; diff --git a/zone/command.cpp b/zone/command.cpp index 1b8ce3365..9ee4e6d11 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -7461,8 +7461,8 @@ void command_path(Client *c, const Seperator *sep) if(zone->zonemap) { - VERTEX loc(px, py, pz); - best_z = zone->zonemap->FindBestZ(MAP_ROOT_NODE, loc, nullptr, nullptr); + Map::Vertex loc(px, py, pz); + best_z = zone->zonemap->FindBestZ(loc, nullptr); } else { @@ -7488,8 +7488,8 @@ void command_path(Client *c, const Seperator *sep) if(zone->zonemap) { - VERTEX loc(px, py, pz); - best_z = zone->zonemap->FindBestZ(MAP_ROOT_NODE, loc, nullptr, nullptr); + Map::Vertex loc(px, py, pz); + best_z = zone->zonemap->FindBestZ(loc, nullptr); } else { @@ -7621,8 +7621,8 @@ void command_path(Client *c, const Seperator *sep) { if(c && c->GetTarget()) { - if(zone->pathing->NoHazardsAccurate(VERTEX(c->GetX(),c->GetY(),c->GetZ()), - VERTEX(c->GetTarget()->GetX(),c->GetTarget()->GetY(),c->GetTarget()->GetZ()))) + if (zone->pathing->NoHazardsAccurate(Map::Vertex(c->GetX(), c->GetY(), c->GetZ()), + Map::Vertex(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ()))) { c->Message(0, "No hazards."); } @@ -7698,7 +7698,7 @@ void command_path(Client *c, const Seperator *sep) { Mob *m = c->GetTarget(); - VERTEX Position(m->GetX(), m->GetY(), m->GetZ()); + Map::Vertex Position(m->GetX(), m->GetY(), m->GetZ()); int Node = zone->pathing->FindNearestPathNode(Position); @@ -7831,35 +7831,19 @@ void command_bestz(Client *c, const Seperator *sep) { return; } - NodeRef pnode; - if(c->GetTarget()) { - pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), c->GetTarget()->GetX(), c->GetTarget()->GetY() ); - } else { - pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), c->GetX(), c->GetY() ); - } - if (pnode == NODE_NONE) { - c->Message(0,"Unable to find your node."); - return; - } - - VERTEX me; + Map::Vertex me; me.x = c->GetX(); me.y = c->GetY(); me.z = c->GetZ() + (c->GetSize()==0.0?6:c->GetSize()) * HEAD_POSITION; - VERTEX hit; - VERTEX bme(me); + Map::Vertex hit; + Map::Vertex bme(me); bme.z -= 500; - float best_z = zone->zonemap->FindBestZ(pnode, me, &hit, nullptr); - - float best_z2 = -999990; - if(zone->zonemap->LineIntersectsNode(pnode, me, bme, &hit, nullptr)) { - best_z2 = hit.z; - } + float best_z = zone->zonemap->FindBestZ(me, &hit); if (best_z != -999999) { - c->Message(0,"Z is %.3f or %.3f at (%.3f, %.3f).", best_z, best_z2, me.x, me.y); + c->Message(0,"Z is %.3f at (%.3f, %.3f).", best_z, me.x, me.y); } else { diff --git a/zone/entity.cpp b/zone/entity.cpp index 9103ed157..0b7f41066 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -2554,8 +2554,8 @@ void EntityList::FindPathsToAllNPCs() auto it = npc_list.begin(); while (it != npc_list.end()) { - VERTEX Node0 = zone->pathing->GetPathNodeCoordinates(0, false); - VERTEX Dest(it->second->GetX(), it->second->GetY(), it->second->GetZ()); + Map::Vertex Node0 = zone->pathing->GetPathNodeCoordinates(0, false); + Map::Vertex Dest(it->second->GetX(), it->second->GetY(), it->second->GetZ()); std::list Route = zone->pathing->FindRoute(Node0, Dest); if (Route.size() == 0) printf("Unable to find a route to %s\n", it->second->GetName()); @@ -3557,9 +3557,9 @@ bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z, if (zone->zonemap == nullptr) return true; - VERTEX myloc; - VERTEX oloc; - VERTEX hit; + Map::Vertex myloc; + Map::Vertex oloc; + Map::Vertex hit; myloc.x = cur_x; myloc.y = cur_y; @@ -3572,9 +3572,7 @@ bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z, if (myloc.x == oloc.x && myloc.y == oloc.y && myloc.z == oloc.z) return true; - FACE *onhit; - - if (!zone->zonemap->LineIntersectsZoneNoZLeaps(myloc,oloc,perwalk,&hit,&onhit)) + if (!zone->zonemap->LineIntersectsZoneNoZLeaps(myloc,oloc,perwalk,&hit)) return true; return false; } diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index 57a3de337..2b9a50e59 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -152,11 +152,11 @@ void Mob::CalculateNewFearpoint() { int Node = zone->pathing->GetRandomPathNode(); - VERTEX Loc = zone->pathing->GetPathNodeCoordinates(Node); + Map::Vertex Loc = zone->pathing->GetPathNodeCoordinates(Node); ++Loc.z; - VERTEX CurrentPosition(GetX(), GetY(), GetZ()); + Map::Vertex CurrentPosition(GetX(), GetY(), GetZ()); std::list Route = zone->pathing->FindRoute(CurrentPosition, Loc); @@ -205,429 +205,6 @@ void Mob::CalculateNewFearpoint() } } -//we need to start acting scared... -//old fear function, kept for ref. -/*void Mob::SetFeared(Mob *caster, uint32 duration, bool flee) { - //special args to stop fear - if(caster == nullptr && duration == 0) { - fear_state = fearStateNotFeared; -#ifdef FLEE_HP_RATIO - flee_mode = false; -#endif - safe_delete(fear_path_state); - return; - } - - flee_mode = flee; - - //fear dosent work without at least maps - if(zone->zonemap == nullptr) { - fear_state = fearStateStuck; - return; //just stand there - } - - //if we are allready feared, and we are on a fear grid.. - //then just stay happy on the grid... - if(fear_path_state != nullptr) { - if(fear_state != fearStateGrid) { - LogFile->write(EQEMuLog::Debug, "Umm... %s has a fear path state, but is not in a grid state. Wtf?", GetName()); - fear_state = fearStateGrid; - } - return; - } - - //try to run straight away from the caster - VERTEX hit, fear_vector; - if(FearTryStraight(caster, duration, flee, hit, fear_vector)) { - return; - } - - //OK, so if we just run, we are going to hit something... - //now we have to think a little more. - - //first, try to find a fear node that we can see. - if(zone->pathing != nullptr) { - fear_path_state = new MobFearState(); - if(zone->pathing->FindNearestFear(fear_path_state, GetX(), GetY(), GetZ())) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing Start: found path, moving from (%.2f, %.2f, %.2f) to path node (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), fear_path_state->x, fear_path_state->y, fear_path_state->z); -#endif - //we found a fear node... were on our way.. - cur_wp_x = fear_path_state->x; - cur_wp_y = fear_path_state->y; - cur_wp_z = fear_path_state->z; - fear_state = fearStateGrid; - return; - } - - //we have failed to find a path, so we dont need this.. - safe_delete(fear_path_state); - } - - //if we cannot just run, and we cannot see any paths, then - //we will give one last ditch effort to find a legit path. We - //will run as far as we can away from the player, and hope we - //can see a path from there if not, we will start breaking rules - -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing Start: Hope run from (%.2f, %.2f, %.2f), hit at (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), hit.x, hit.y, hit.z); -#endif - //use the hit point - a little + a little Z as the first waypoint. - cur_wp_x = hit.x - fear_vector.x * 2; - cur_wp_y = hit.y - fear_vector.y * 2; - cur_wp_z = GetZ(); - fear_state = fearStateRunning; -} -//old fear function, kept for ref. -bool Mob::FearTryStraight(Mob *caster, uint32 duration, bool flee, VERTEX &hit, VERTEX &fear_vector) { - //gotta have somebody to run from - if(caster == nullptr) - return(false); - - //our goal is to run along this vector... - fear_vector.x = GetX() - caster->GetX(); - fear_vector.y = GetY() - caster->GetY(); - fear_vector.z = 0; //I dont see any reason to use Z - float mag = sqrtf(fear_vector.x*fear_vector.x + fear_vector.y*fear_vector.y); - fear_vector.x /= mag; - fear_vector.y /= mag; - - //now see if we can just run without hitting anything... - VERTEX start, end; - start.x = GetX(); - start.y = GetY(); - start.z = GetZ() + 5.0; //raise up a little over small bumps - - //distance moved per movement tic. - float distance = NPC_SPEED_MULTIPLIER * GetFearSpeed(); - //times number of movement tics in the spell. - distance *= float(duration) / float(AImovement_duration); - - end.x = start.x + fear_vector.x * distance; - end.y = start.y + fear_vector.y * distance; - end.z = start.z; - - if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &hit, nullptr)) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing Start: can run entire vector from (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), end.x, end.y, end.z); -#endif - //no hit, we can run this whole vector. - cur_wp_x = end.x; - cur_wp_y = end.y; - cur_wp_z = GetZ(); - fear_state = fearStateRunningForever; - return(true); //were done, nothing difficult needed. - } - - return(false); -} - -//old fear function, kept for ref. -void Mob::CalculateFearPosition() { - if(zone->zonemap == nullptr || fear_state == fearStateStuck) { - return; //just stand there - } - - //This is the entire movement section, right here: - if (cur_wp_x != GetX() && cur_wp_y != GetY()) { - // not at waypoint yet, so keep moving - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetFearSpeed(), true); - return; - } - - - - //we have reached our waypoint, now what? - //figure out a new waypoint to run at... - - if(fear_state == fearStateRunningForever) { - if(flee_mode) { - //a fleeing mob may run away again - VERTEX hit, fear_vector; - if(FearTryStraight(GetHateTop(), FLEE_RUN_DURATION, true, hit, fear_vector)) - return; //we are running again - //else, we need to find a grid, so act like we were on a hope run - fear_state = fearStateRunning; - } -#ifndef FORCE_FEAR_TO_RUN - else { - //we were supposed to run forever, but we did not... - //should re-fear ourself or something?? - fear_state = fearStateStuck; - return; - } -#endif - } - - //first see if we are on a path. if so our life is easy - if(fear_state == fearStateGrid && fear_path_state) { - //assume that we have zone->pathing since we got to this state. - if(!zone->pathing->NextFearPath(fear_path_state)) { - //this is bad, we were on a path and now its giving us - //an error... we dont have a good way to deal with this - fear_state = fearStateStuck; - return; - } -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: on path, moving from (%.2f, %.2f, %.2f) to path node (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), fear_path_state->x, fear_path_state->y, fear_path_state->z); -#endif - //we found a fear node... were on our way.. - cur_wp_x = fear_path_state->x; - cur_wp_y = fear_path_state->y; - cur_wp_z = fear_path_state->z; - - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetFearSpeed(), true); - return; - } - - //the only valid state left is fearStateRunning, where we try to - //find a grid once we reach our waypoint, which we have.. - if(fear_state != fearStateRunning) { - //wtf... unknown state - LogFile->write(EQEMuLog::Debug, "Fear Pathing: Reached our fear waypoint, but we are in an unknown state %d... stopping.", fear_state); - fear_state = fearStateStuck; - return; - } - - //we wanted to try to find a waypoint now, so lets try.. - if(zone->pathing != nullptr) { - fear_path_state = new MobFearState(); - - if(zone->pathing->FindNearestFear(fear_path_state, GetX(), GetY(), GetZ())) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: ran to find path, moving from (%.2f, %.2f, %.2f) to path node (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), fear_path_state->x, fear_path_state->y, fear_path_state->z); -#endif - //we found a fear node... were on our way.. - cur_wp_x = fear_path_state->x; - cur_wp_y = fear_path_state->y; - cur_wp_z = fear_path_state->z; - fear_state = fearStateGrid; - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetFearSpeed(), true); - return; - } - - //if we get here... all valid methods have failed - -#ifdef FORCE_FEAR_TO_RUN - //ok, now we start making shit up - - //for now, we will limit our bullshitting to ignoring LOS - //when finding a pathing node, we SHOULD always get something.. - //do not force a path if we are fleeing - if(!flee_mode && zone->pathing->FindNearestFear(fear_path_state, GetX(), GetY(), GetZ(), false)) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: Bullshit Path from (%.2f, %.2f, %.2f) to path node (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), fear_path_state->x, fear_path_state->y, fear_path_state->z); -#endif - //we found a fear node... were on our way.. - cur_wp_x = fear_path_state->x; - cur_wp_y = fear_path_state->y; - cur_wp_z = fear_path_state->z; - fear_state = fearStateGrid; - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetFearSpeed(), true); - return; - } -#endif //FORCE_FEAR_TO_RUN - - //we have failed to find a path once again, so we dont need this.. - safe_delete(fear_path_state); - } - - //if we get HERE... then NOTHING worked... just stick - fear_state = fearStateStuck; - - //end of function, everything else is #ifdef'd out -//} - -//I dont wanna get rid of this right now because it was a lot of hard -//work to write... but it dosent work reliably, so oh well.. -#ifdef OLD_FEAR_PATHING - /* - The idea... - - try to run along fear vector. - If we can see along it, run - otherwise, try to walk up a hill along the same vector - then try to move along a wall along largest component of FV - if cant move, change stae to stuck. - - once we know a place to run, use the waypoint code to do it - then if combat ends, we will reach the waypoint and - - - */ - /* - //first try our original fear vector again... - VERTEX start, end, hit, normalhit; - start.x = GetX() - fear_vector.x * 0.4; - start.y = GetY() - fear_vector.y * 0.4; - start.z = GetZ() + 6.0; //raise up a little over small bumps - - end.x = start.x + fear_vector.x * 10; - end.y = start.y + fear_vector.y * 10; - end.z = start.z; - - if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &normalhit, nullptr)) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) normal run to (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), end.x, end.y, end.z); -#endif - //we can run along this vector without hitting anything... - cur_wp_x = end.x; - cur_wp_y = end.y; - cur_wp_z = end.z - 6.0; - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true); - return; - } - //see if we can make ANY useful progress along that vector - - //first, adjust normalhit to back up a little bit - //so we dont run through the wall - normalhit.x -= 0.4 * fear_vector.x; - normalhit.y -= 0.4 * fear_vector.y; - - float xd = normalhit.x - start.x; - if(xd < 0) - xd = 0 - xd; - float yd = normalhit.y - start.y; - if(yd < 0) - yd = 0 - yd; - - //this 2 is arbitrary - if((xd+yd) > 2.0) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) small run to (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), cur_wp_x, cur_wp_y, cur_wp_z); -#endif - cur_wp_x = normalhit.x; - cur_wp_y = normalhit.y; - cur_wp_z = GetZ(); - - //try and fix up the Z coord if possible - //not sure if this is worth it, since it prolly isnt up much - - NodeRef c = zone->zonemap->SeekNode(zone->zonemap->GetRoot(), end.x, end.y); - if(c != NODE_NONE) { - cur_wp_z = zone->zonemap->FindBestZ(c, end, &hit, nullptr); - if(cur_wp_z < start.z) - cur_wp_z = end.z; //revert on error - } - - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true); - return; - } - -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) normal hit at (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), normalhit.x, normalhit.y, normalhit.z); -#endif - - //if we get here, we cannot run along our normal vector... - //try up hill first - - /* - while this uphill stuff works great in outdoor zones, - it totally breaks dungeons... - - float speed = GetRunspeed(); - end.x = start.x + fear_vector.x * speed; - end.y = start.y + fear_vector.y * speed; - end.z = start.z + speed + speed; - - if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &hit, nullptr)) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) up hill run to (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), end.x, end.y, end.z); -#endif - //we can run along this vector without hitting anything... - cur_wp_x = end.x - 0.4 * fear_vector.x; - cur_wp_y = end.y - 0.4 * fear_vector.y; - cur_wp_z = end.z; - - //try and fix up the Z coord if possible - //not sure if this is worth it, since it prolly isnt up much - - NodeRef c = zone->zonemap->SeekNode(zone->zonemap->GetRoot(), end.x, end.y); - if(c != NODE_NONE) { - cur_wp_z = zone->zonemap->FindBestZ(c, end, &hit, nullptr); - if(cur_wp_z < start.z) - cur_wp_z = end.z; //revert on error - } - - - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true); - return; - } - */ - /* - //cant run along our vector at all.... - //one last ditch effort... try to move to the side a little - //along the minor component of the fear vector. - //try it in one direction first... - if(fear_vector.x < fear_vector.y) { - end.x = start.x + fear_vector.x * 3; - end.y = start.y; - } else { - end.x = start.x; - end.y = start.y + fear_vector.y * 3; - } - end.z = start.z + 3; //a little lift as always - - if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &hit, nullptr)) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) strafe 1 to (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), end.x, end.y, end.z); -#endif - //we can run along this vector without hitting anything... - cur_wp_x = end.x - 0.4 * fear_vector.x; - cur_wp_y = end.y - 0.4 * fear_vector.y; - cur_wp_z = end.z - 3; - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true); - return; - } - - //now the other... - if(fear_vector.x < fear_vector.y) { - end.x = start.x + fear_vector.x * 3; - end.y = start.y; - } else { - end.x = start.x; - end.y = start.y + fear_vector.y * 3; - } - end.z = start.z + 3; //a little lift as always - - if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &hit, nullptr)) { -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) strafe 2 to (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), end.x, end.y, end.z); -#endif - //we can run along this vector without hitting anything... - cur_wp_x = end.x - 0.4 * fear_vector.x; - cur_wp_y = end.y - 0.4 * fear_vector.y; - cur_wp_z = end.z - 3; - CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true); - return; - } - - //if we get here... we have wasted enough CPU cycles - //just call it quits on fear pathing... - - //send them to normalhit and then stop - cur_wp_x = normalhit.x; - cur_wp_y = normalhit.y; - cur_wp_z = GetZ(); - fear_state = fearStateRunningToStick; -#ifdef FEAR_PATHING_DEBUG - LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) final move to (%.2f, %.2f, %.2f)", - GetX(), GetY(), GetZ(), normalhit.x, normalhit.y, normalhit.z); -#endif -#endif //OLD_FEAR_PATHING -}*/ diff --git a/zone/forage.cpp b/zone/forage.cpp index ee0f45fb4..de8a2c5a5 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -225,7 +225,7 @@ bool Client::CanFish() { return false; } - if(zone->zonemap!=nullptr && zone->watermap != nullptr && RuleB(Watermap, CheckForWaterWhenFishing)) { + if(zone->zonemap != nullptr && zone->watermap != nullptr && RuleB(Watermap, CheckForWaterWhenFishing)) { float RodX, RodY, RodZ; // Tweak Rod and LineLength if required const float RodLength = RuleR(Watermap, FishingRodLength); @@ -240,24 +240,22 @@ bool Client::CanFish() { // Do BestZ to find where the line hanging from the rod intersects the water (if it is water). // and go 1 unit into the water. - VERTEX dest; + Map::Vertex dest; dest.x = RodX; dest.y = RodY; dest.z = z_pos+10; - NodeRef n = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), dest.x, dest.y); - if(n != NODE_NONE) { - RodZ = zone->zonemap->FindBestZ(n, dest, nullptr, nullptr) - 1; - bool in_lava = zone->watermap->InLava(RodX, RodY, RodZ); - bool in_water = zone->watermap->InWater(RodX, RodY, RodZ) || zone->watermap->InVWater(RodX, RodY, RodZ); - //Message(0, "Rod is at %4.3f, %4.3f, %4.3f, InWater says %d, InLava says %d", RodX, RodY, RodZ, in_water, in_lava); - if (in_lava) { - Message_StringID(MT_Skills, FISHING_LAVA); //Trying to catch a fire elemental or something? - return false; - } - if((!in_water) || (z_pos-RodZ)>LineLength) { //Didn't hit the water OR the water is too far below us - Message_StringID(MT_Skills, FISHING_LAND); //Trying to catch land sharks perhaps? - return false; - } + + RodZ = zone->zonemap->FindBestZ(dest, nullptr) - 1; + bool in_lava = zone->watermap->InLava(RodX, RodY, RodZ); + bool in_water = zone->watermap->InWater(RodX, RodY, RodZ) || zone->watermap->InVWater(RodX, RodY, RodZ); + //Message(0, "Rod is at %4.3f, %4.3f, %4.3f, InWater says %d, InLava says %d", RodX, RodY, RodZ, in_water, in_lava); + if (in_lava) { + Message_StringID(MT_Skills, FISHING_LAVA); //Trying to catch a fire elemental or something? + return false; + } + if((!in_water) || (z_pos-RodZ)>LineLength) { //Didn't hit the water OR the water is too far below us + Message_StringID(MT_Skills, FISHING_LAND); //Trying to catch land sharks perhaps? + return false; } } return true; diff --git a/zone/map.cpp b/zone/map.cpp new file mode 100644 index 000000000..088fc0c45 --- /dev/null +++ b/zone/map.cpp @@ -0,0 +1,268 @@ +#include "../common/debug.h" +#include "../common/MiscFunctions.h" +#include "map.h" +#include "RaycastMesh.h" +#include "zone.h" +#include +#include +#include +#include + +struct Map::impl +{ + RaycastMesh *rm; +}; + +Map::Map() { + imp = nullptr; +} + +Map::~Map() { + if(imp) { + imp->rm->release(); + } +} + +float Map::FindBestZ(Vertex &start, Vertex *result) const { + if (!imp) + return false; + + Vertex tmp; + if(!result) + result = &tmp; + + start.z += RuleI(Map, FindBestZHeightAdjust); + Vertex from = { start.x, start.y, start.z }; + Vertex to = { start.x, start.y, BEST_Z_INVALID }; + float hit_distance; + bool hit = false; + + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance); + if(hit) { + return result->z; + } + + // Find nearest Z above us + + to.z = -BEST_Z_INVALID; + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance); + if (hit) + { + return result->z; + } + + return BEST_Z_INVALID; +} + +bool Map::LineIntersectsZone(Vertex start, Vertex end, float step, Vertex *result) const { + if(!imp) + return false; + return imp->rm->raycast((const RmReal*)&start, (const RmReal*)&end, (RmReal*)result, nullptr, nullptr); +} + +bool Map::LineIntersectsZoneNoZLeaps(Vertex start, Vertex end, float step_mag, Vertex *result) const { + if (!imp) + return false; + + float z = BEST_Z_INVALID; + Vertex step; + Vertex cur; + cur.x = start.x; + cur.y = start.y; + cur.z = start.z; + + step.x = end.x - start.x; + step.y = end.y - start.y; + step.z = end.z - start.z; + float factor = step_mag / sqrt(step.x*step.x + step.y*step.y + step.z*step.z); + + step.x *= factor; + step.y *= factor; + step.z *= factor; + + int steps = 0; + + if (step.x > 0 && step.x < 0.001f) + step.x = 0.001f; + if (step.y > 0 && step.y < 0.001f) + step.y = 0.001f; + if (step.z > 0 && step.z < 0.001f) + step.z = 0.001f; + if (step.x < 0 && step.x > -0.001f) + step.x = -0.001f; + if (step.y < 0 && step.y > -0.001f) + step.y = -0.001f; + if (step.z < 0 && step.z > -0.001f) + step.z = -0.001f; + + //while we are not past end + //always do this once, even if start == end. + while(cur.x != end.x || cur.y != end.y || cur.z != end.z) + { + steps++; + Vertex me; + me.x = cur.x; + me.y = cur.y; + me.z = cur.z; + Vertex hit; + + float best_z = FindBestZ(me, &hit); + float diff = best_z - z; + diff = diff < 0 ? -diff : diff; + + if (z == -999999 || best_z == -999999 || diff < 12.0) + z = best_z; + else + return true; + + //look at current location + if(LineIntersectsZone(start, end, step_mag, result)) + { + return true; + } + + //move 1 step + if (cur.x != end.x) + cur.x += step.x; + if (cur.y != end.y) + cur.y += step.y; + if (cur.z != end.z) + cur.z += step.z; + + //watch for end conditions + if ( (cur.x > end.x && end.x >= start.x) || (cur.x < end.x && end.x <= start.x) || (step.x == 0) ) { + cur.x = end.x; + } + if ( (cur.y > end.y && end.y >= start.y) || (cur.y < end.y && end.y <= start.y) || (step.y == 0) ) { + cur.y = end.y; + } + if ( (cur.z > end.z && end.z >= start.z) || (cur.z < end.z && end.z < start.z) || (step.z == 0) ) { + cur.z = end.z; + } + } + + //walked entire line and didnt run into anything... + return false; +} + +bool Map::CheckLoS(Vertex myloc, Vertex oloc) const { + if(!imp) + return false; + + return !imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, nullptr, nullptr); +} + +Map *Map::LoadMapFile(std::string file) { + std::string filename = MAP_DIR; + filename += "/"; + std::transform(file.begin(), file.end(), file.begin(), ::tolower); + filename += file; + filename += ".map"; + + Map *m = new Map(); + if (m->Load(filename)) { + return m; + } + + delete m; + return nullptr; +} + +bool Map::Load(std::string filename) { + FILE *f = fopen(filename.c_str(), "rb"); + if(f) { + uint32_t version; + if(fread(&version, sizeof(version), 1, f) != 1) { + fclose(f); + return false; + } + + if(version == 0x01000000) { + bool v = LoadV1(f); + fclose(f); + return v; + } else if(version == 0x02000000) { + bool v = LoadV2(f); + fclose(f); + return v; + } else { + fclose(f); + return false; + } + } + + return false; +} + +bool Map::LoadV1(FILE *f) { + uint32_t face_count; + uint16_t node_count; + uint32_t facelist_count; + + if(fread(&face_count, sizeof(face_count), 1, f) != 1) { + return false; + } + + if(fread(&node_count, sizeof(node_count), 1, f) != 1) { + return false; + } + + if(fread(&facelist_count, sizeof(facelist_count), 1, f) != 1) { + return false; + } + + std::vector verts; + std::vector indices; + for(uint32_t i = 0; i < face_count; ++i) { + Vertex a; + Vertex b; + Vertex c; + float normals[4]; + if(fread(&a, sizeof(Vertex), 1, f) != 1) { + return false; + } + + if(fread(&b, sizeof(Vertex), 1, f) != 1) { + return false; + } + + if(fread(&c, sizeof(Vertex), 1, f) != 1) { + return false; + } + + if(fread(normals, sizeof(normals), 1, f) != 1) { + return false; + } + + size_t sz = verts.size(); + verts.push_back(a); + indices.push_back((uint32_t)sz); + + verts.push_back(b); + indices.push_back((uint32_t)sz + 1); + + verts.push_back(c); + indices.push_back((uint32_t)sz + 2); + } + + if(imp) { + imp->rm->release(); + imp->rm = nullptr; + } else { + imp = new impl; + } + + imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0]); + + if(!imp->rm) { + delete imp; + imp = nullptr; + return false; + } + + return true; +} + +bool Map::LoadV2(FILE *f) { + return false; +} diff --git a/zone/map.h b/zone/map.h new file mode 100644 index 000000000..dd7b2b50c --- /dev/null +++ b/zone/map.h @@ -0,0 +1,67 @@ +/* + EQEMu: Everquest Server Emulator + + Copyright (C) 2001-2014 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 + +*/ + +#ifndef ZONE_MAP_H +#define ZONE_MAP_H + +#include +#include + +#define BEST_Z_INVALID -99999 + +class Map +{ +public: +#pragma pack(1) + struct Vertex + { + Vertex() : x(0.0f), y(0.0f), z(0.0f) { } + Vertex(float _x, float _y, float _z) : x(_x), y(_y), z(_z) { } + ~Vertex() { } + bool operator==(const Vertex &v) const + { + return((v.x == x) && (v.y == y) && (v.z == z)); + } + + float x; + float y; + float z; + }; +#pragma pack() + + Map(); + ~Map(); + + float FindBestZ(Vertex &start, Vertex *result) const; + bool LineIntersectsZone(Vertex start, Vertex end, float step, Vertex *result) const; + bool LineIntersectsZoneNoZLeaps(Vertex start, Vertex end, float step_mag, Vertex *result) const; + bool CheckLoS(Vertex myloc, Vertex oloc) const; + bool Load(std::string filename); + static Map *LoadMapFile(std::string file); +private: + bool LoadV1(FILE *f); + bool LoadV2(FILE *f); + + struct impl; + impl *imp; +}; + +#endif diff --git a/zone/mob.cpp b/zone/mob.cpp index a77589d63..c6085acbf 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2933,22 +2933,17 @@ void Mob::SetTarget(Mob* mob) { float Mob::FindGroundZ(float new_x, float new_y, float z_offset) { float ret = -999999; - if (zone->zonemap != 0) + if (zone->zonemap != nullptr) { - NodeRef pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), new_x, new_y ); - if (pnode != NODE_NONE) + Map::Vertex me; + me.x = new_x; + me.y = new_y; + me.z = z_pos+z_offset; + Map::Vertex hit; + float best_z = zone->zonemap->FindBestZ(me, &hit); + if (best_z != -999999) { - VERTEX me; - me.x = new_x; - me.y = new_y; - me.z = z_pos+z_offset; - VERTEX hit; - FACE *onhit; - float best_z = zone->zonemap->FindBestZ(pnode, me, &hit, &onhit); - if (best_z != -999999) - { - ret = best_z; - } + ret = best_z; } } return ret; @@ -2960,20 +2955,15 @@ float Mob::GetGroundZ(float new_x, float new_y, float z_offset) float ret = -999999; if (zone->zonemap != 0) { - NodeRef pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), new_x, new_y ); - if (pnode != NODE_NONE) + Map::Vertex me; + me.x = new_x; + me.y = new_y; + me.z = z_pos+z_offset; + Map::Vertex hit; + float best_z = zone->zonemap->FindBestZ(me, &hit); + if (best_z != -999999) { - VERTEX me; - me.x = new_x; - me.y = new_y; - me.z = z_pos+z_offset; - VERTEX hit; - FACE *onhit; - float best_z = zone->zonemap->FindBestZ(pnode, me, &hit, &onhit); - if (best_z != -999999) - { - ret = best_z; - } + ret = best_z; } } return ret; diff --git a/zone/mob.h b/zone/mob.h index f636b5ea7..01d0c87b1 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -417,7 +417,7 @@ public: void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu); void SendPosition(); void SetFlyMode(uint8 flymode); - inline void Teleport(VERTEX NewPosition) { x_pos = NewPosition.x; y_pos = NewPosition.y; + inline void Teleport(Map::Vertex NewPosition) { x_pos = NewPosition.x; y_pos = NewPosition.y; z_pos = NewPosition.z; }; //AI @@ -447,7 +447,6 @@ public: void ClearFeignMemory(); void PrintHateListToClient(Client *who) { hate_list.PrintToClient(who); } std::list& GetHateList() { return hate_list.GetHateList(); } - bool CheckLos(Mob* other); bool CheckLosFN(Mob* other); bool CheckLosFN(float posX, float posY, float posZ, float mobSize); inline void SetChanged() { pLastChange = Timer::GetCurrentTime(); } @@ -981,7 +980,7 @@ protected: bool HasDied(); void CalculateNewFearpoint(); float FindGroundZ(float new_x, float new_y, float z_offset=0.0); - VERTEX UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached); + Map::Vertex UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached); void PrintRoute(); void UpdateRuneFlags(); @@ -1146,8 +1145,8 @@ protected: // Pathing // - VERTEX PathingDestination; - VERTEX PathingLastPosition; + Map::Vertex PathingDestination; + Map::Vertex PathingLastPosition; int PathingLoopCount; int PathingLastNodeVisited; std::list Route; diff --git a/zone/pathing.cpp b/zone/pathing.cpp index 404b3d898..4e5ae4cbd 100644 --- a/zone/pathing.cpp +++ b/zone/pathing.cpp @@ -21,7 +21,7 @@ extern Zone *zone; -float VertexDistance(VERTEX a, VERTEX b) +float VertexDistance(Map::Vertex a, Map::Vertex b) { float xdist = a.x - b.x; float ydist = a.y - b.y; @@ -29,7 +29,7 @@ float VertexDistance(VERTEX a, VERTEX b) return sqrtf(xdist * xdist + ydist * ydist + zdist * zdist); } -float VertexDistanceNoRoot(VERTEX a, VERTEX b) +float VertexDistanceNoRoot(Map::Vertex a, Map::Vertex b) { float xdist = a.x - b.x; float ydist = a.y - b.y; @@ -187,9 +187,9 @@ void PathManager::PrintPathing() } } -VERTEX PathManager::GetPathNodeCoordinates(int NodeNumber, bool BestZ) +Map::Vertex PathManager::GetPathNodeCoordinates(int NodeNumber, bool BestZ) { - VERTEX Result; + Map::Vertex Result; if(NodeNumber < Head.PathNodeCount) { @@ -335,12 +335,12 @@ std::list PathManager::FindRoute(int startID, int endID) } -bool CheckLOSBetweenPoints(VERTEX start, VERTEX end) { +bool CheckLOSBetweenPoints(Map::Vertex start, Map::Vertex end) { - VERTEX hit; - FACE *face; + Map::Vertex hit; - if((zone->zonemap) && (zone->zonemap->LineIntersectsZone(start, end, 1, &hit, &face))) return false; + if((zone->zonemap) && (zone->zonemap->LineIntersectsZone(start, end, 1, &hit))) + return false; return true; } @@ -350,7 +350,7 @@ bool SortPathNodesByDistance(PathNodeSortStruct n1, PathNodeSortStruct n2) return n1.Distance < n2.Distance; } -std::list PathManager::FindRoute(VERTEX Start, VERTEX End) +std::list PathManager::FindRoute(Map::Vertex Start, Map::Vertex End) { _log(PATHING__DEBUG, "FindRoute(%8.3f, %8.3f, %8.3f, %8.3f, %8.3f, %8.3f)", Start.x, Start.y, Start.z, End.x, End.y, End.z); @@ -388,7 +388,7 @@ std::list PathManager::FindRoute(VERTEX Start, VERTEX End) { _log(PATHING__DEBUG, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id); - if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Iterator).id].v, 1.0f, nullptr, nullptr)) + if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Iterator).id].v, 1.0f, nullptr)) { ClosestPathNodeToStart = (*Iterator).id; break; @@ -429,7 +429,7 @@ std::list PathManager::FindRoute(VERTEX Start, VERTEX End) End.x, End.y, End.z, PathNodes[(*Iterator).id].v.x, PathNodes[(*Iterator).id].v.y, PathNodes[(*Iterator).id].v.z); - if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Iterator).id].v, 1.0f, nullptr, nullptr)) + if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Iterator).id].v, 1.0f, nullptr)) { ClosestPathNodeToEnd = (*Iterator).id; break; @@ -469,7 +469,7 @@ std::list PathManager::FindRoute(VERTEX Start, VERTEX End) if((*Second) < 0) break; - if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Second)].v, 1.0f, nullptr, nullptr) + if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Second)].v, 1.0f, nullptr) && zone->pathing->NoHazards(Start, PathNodes[(*Second)].v)) { noderoute.erase(First); @@ -502,7 +502,7 @@ std::list PathManager::FindRoute(VERTEX Start, VERTEX End) if((*Second) < 0) break; - if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Second)].v, 1.0f, nullptr, nullptr) + if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Second)].v, 1.0f, nullptr) && zone->pathing->NoHazards(End, PathNodes[(*Second)].v)) { noderoute.erase(First); @@ -652,19 +652,19 @@ void PathManager::SimpleMeshTest() fflush(stdout); } -VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChanged, bool &NodeReached) +Map::Vertex Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChanged, bool &NodeReached) { WaypointChanged = false; NodeReached = false; - VERTEX NodeLoc; + Map::Vertex NodeLoc; - VERTEX From(GetX(), GetY(), GetZ()); + Map::Vertex From(GetX(), GetY(), GetZ()); - VERTEX HeadPosition(From.x, From.y, From.z + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION); + Map::Vertex HeadPosition(From.x, From.y, From.z + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION); - VERTEX To(ToX, ToY, ToZ); + Map::Vertex To(ToX, ToY, ToZ); bool SameDestination = (To == PathingDestination); @@ -761,7 +761,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo if((Distance <= RuleR(Pathing, MinDistanceForLOSCheckShort)) && (ABS(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold))) { - if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr, nullptr)) + if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr)) PathingLOSState = HaveLOS; else PathingLOSState = NoLOS; @@ -854,7 +854,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo if((Distance <= RuleR(Pathing, MinDistanceForLOSCheckShort)) && (ABS(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold))) { - if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr, nullptr)) + if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr)) PathingLOSState = HaveLOS; else PathingLOSState = NoLOS; @@ -893,7 +893,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo && (ABS(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold))) { mlog(PATHING__DEBUG, " Checking for short LOS at distance %8.3f.", Distance); - if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr, nullptr)) + if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr)) PathingLOSState = HaveLOS; else PathingLOSState = NoLOS; @@ -1046,7 +1046,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo { mlog(PATHING__DEBUG, " Checking for long LOS at distance %8.3f.", Distance); - if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr, nullptr)) + if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr)) PathingLOSState = HaveLOS; else PathingLOSState = NoLOS; @@ -1090,7 +1090,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo } -int PathManager::FindNearestPathNode(VERTEX Position) +int PathManager::FindNearestPathNode(Map::Vertex Position) { // Find the nearest PathNode we have LOS to. @@ -1126,7 +1126,7 @@ int PathManager::FindNearestPathNode(VERTEX Position) { _log(PATHING__DEBUG, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id); - if(!zone->zonemap->LineIntersectsZone(Position, PathNodes[(*Iterator).id].v, 1.0f, nullptr, nullptr)) + if(!zone->zonemap->LineIntersectsZone(Position, PathNodes[(*Iterator).id].v, 1.0f, nullptr)) { ClosestPathNodeToStart = (*Iterator).id; break; @@ -1140,13 +1140,13 @@ int PathManager::FindNearestPathNode(VERTEX Position) return ClosestPathNodeToStart; } -bool PathManager::NoHazards(VERTEX From, VERTEX To) +bool PathManager::NoHazards(Map::Vertex From, Map::Vertex To) { // Test the Z coordinate at the mid point. // - VERTEX MidPoint((From.x + To.x) / 2, (From.y + To.y) / 2, From.z); + Map::Vertex MidPoint((From.x + To.x) / 2, (From.y + To.y) / 2, From.z); - float NewZ = zone->zonemap->FindBestZ(MAP_ROOT_NODE, MidPoint, nullptr, nullptr); + float NewZ = zone->zonemap->FindBestZ(MidPoint, nullptr); if(ABS(NewZ - From.z) > RuleR(Pathing, ZDiffThreshold)) { @@ -1164,10 +1164,10 @@ bool PathManager::NoHazards(VERTEX From, VERTEX To) return true; } -bool PathManager::NoHazardsAccurate(VERTEX From, VERTEX To) +bool PathManager::NoHazardsAccurate(Map::Vertex From, Map::Vertex To) { float stepx, stepy, stepz, curx, cury, curz; - VERTEX cur = From; + Map::Vertex cur = From; float last_z = From.z; float step_size = 1.0; @@ -1181,13 +1181,13 @@ bool PathManager::NoHazardsAccurate(VERTEX From, VERTEX To) stepy = (float)To.y - cury; stepz = (float)To.z - curz; float factor = sqrt(stepx*stepx + stepy*stepy + stepz*stepz); - stepx = (stepx/factor)*step_size; - stepy = (stepy/factor)*step_size; - stepz = (stepz/factor)*step_size; + stepx = (stepx / factor)*step_size; + stepy = (stepy / factor)*step_size; + stepz = (stepz / factor)*step_size; - VERTEX TestPoint(curx, cury, curz); - float NewZ = zone->zonemap->FindBestZ(MAP_ROOT_NODE, TestPoint, nullptr, nullptr); - if(ABS(NewZ - last_z) > 5.0) + Map::Vertex TestPoint = { curx, cury, curz }; + float NewZ = zone->zonemap->FindBestZ(TestPoint, nullptr); + if (ABS(NewZ - last_z) > 5.0f) { _log(PATHING__DEBUG, " HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Best Z %8.3f, Z Change is %8.3f", From.x, From.y, From.z, TestPoint.x, TestPoint.y, TestPoint.z, NewZ, NewZ - From.z); @@ -1195,49 +1195,45 @@ bool PathManager::NoHazardsAccurate(VERTEX From, VERTEX To) } last_z = NewZ; - if(zone->watermap) + if (zone->watermap) { - NodeRef n = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), TestPoint.x, TestPoint.y); - if(n != NODE_NONE) + if (zone->watermap->InLiquid(From.x, From.y, From.z) || zone->watermap->InLiquid(To.x, To.y, To.z)) { - if(zone->watermap->InLiquid(From.x, From.y, From.z) || zone->watermap->InLiquid(To.x, To.y, To.z)) - { - break; - } + break; + } - if(zone->watermap->InLiquid(TestPoint.x, TestPoint.y, NewZ)) + if (zone->watermap->InLiquid(TestPoint.x, TestPoint.y, NewZ)) + { + Map::Vertex TestPointWater = { TestPoint.x, TestPoint.y, NewZ - 0.5f }; + Map::Vertex TestPointWaterDest = TestPointWater; + Map::Vertex hit; + TestPointWaterDest.z -= 500; + float best_z2 = -999990; + if (zone->zonemap->LineIntersectsZone(TestPointWater, TestPointWaterDest, 1.0f, &hit)) { - VERTEX TestPointWater(TestPoint.x, TestPoint.y, NewZ-0.5); - VERTEX TestPointWaterDest(TestPointWater); - VERTEX hit; - TestPointWaterDest.z -= 500; - float best_z2 = -999990; - if(zone->zonemap->LineIntersectsNode(n, TestPointWater, TestPointWaterDest, &hit, nullptr)) + best_z2 = hit.z; + } + if (best_z2 == -999990) + { + _log(PATHING__DEBUG, " HAZARD DETECTED, really deep water/lava!"); + return false; + } + else + { + if (ABS(NewZ - best_z2) > RuleR(Pathing, ZDiffThreshold)) { - best_z2 = hit.z; - } - if(best_z2 == -999990) - { - _log(PATHING__DEBUG, " HAZARD DETECTED, really deep water/lava!"); + _log(PATHING__DEBUG, " HAZARD DETECTED, water is fairly deep at %8.3f units deep", ABS(NewZ - best_z2)); return false; } else { - if(ABS(NewZ - best_z2) > RuleR(Pathing, ZDiffThreshold)) - { - _log(PATHING__DEBUG, " HAZARD DETECTED, water is fairly deep at %8.3f units deep", ABS(NewZ - best_z2)); - return false; - } - else - { - _log(PATHING__DEBUG, " HAZARD NOT DETECTED, water is shallow at %8.3f units deep", ABS(NewZ - best_z2)); - } + _log(PATHING__DEBUG, " HAZARD NOT DETECTED, water is shallow at %8.3f units deep", ABS(NewZ - best_z2)); } } - else - { - _log(PATHING__DEBUG, "Hazard point not in water or lava!"); - } + } + else + { + _log(PATHING__DEBUG, "Hazard point not in water or lava!"); } } else @@ -1253,12 +1249,11 @@ bool PathManager::NoHazardsAccurate(VERTEX From, VERTEX To) cur.y = cury; cur.z = curz; - if(ABS(curx - To.x) < step_size) cur.x = To.x; - if(ABS(cury - To.y) < step_size) cur.y = To.y; - if(ABS(curz - To.z) < step_size) cur.z = To.z; + if (ABS(curx - To.x) < step_size) cur.x = To.x; + if (ABS(cury - To.y) < step_size) cur.y = To.y; + if (ABS(curz - To.z) < step_size) cur.z = To.z; - } - while(cur.x != To.x || cur.y != To.y || cur.z != To.z); + } while (cur.x != To.x || cur.y != To.y || cur.z != To.z); return true; } @@ -2001,8 +1996,8 @@ void PathManager::MoveNode(Client *c) if(zone->zonemap) { - VERTEX loc(c->GetX(), c->GetY(), c->GetZ()); - Node->bestz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, loc, nullptr, nullptr); + Map::Vertex loc(c->GetX(), c->GetY(), c->GetZ()); + Node->bestz = zone->zonemap->FindBestZ(loc, nullptr); } else { @@ -2072,14 +2067,14 @@ bool PathManager::NodesConnected(PathNode *a, PathNode *b) return false; } -bool PathManager::CheckLosFN(VERTEX a, VERTEX b) +bool PathManager::CheckLosFN(Map::Vertex a, Map::Vertex b) { if(zone->zonemap) { - VERTEX hit; + Map::Vertex hit; - VERTEX myloc; - VERTEX oloc; + Map::Vertex myloc; + Map::Vertex oloc; myloc.x = a.x; myloc.y = a.y; @@ -2090,7 +2085,7 @@ bool PathManager::CheckLosFN(VERTEX a, VERTEX b) oloc.z = b.z; - if(zone->zonemap->LineIntersectsZone(myloc, oloc, 1.0f, nullptr, nullptr)) + if(zone->zonemap->LineIntersectsZone(myloc, oloc, 1.0f, nullptr)) { return false; } diff --git a/zone/pathing.h b/zone/pathing.h index e4daf9592..833a6ef96 100644 --- a/zone/pathing.h +++ b/zone/pathing.h @@ -31,7 +31,7 @@ struct NeighbourNode { struct PathNode { uint16 id; - VERTEX v; + Map::Vertex v; float bestz; NeighbourNode Neighbours[PATHNODENEIGHBOURS]; }; @@ -61,17 +61,17 @@ public: static PathManager *LoadPathFile(const char *ZoneName); bool loadPaths(FILE *fp); void PrintPathing(); - std::list FindRoute(VERTEX Start, VERTEX End); + std::list FindRoute(Map::Vertex Start, Map::Vertex End); std::list FindRoute(int startID, int endID); - VERTEX GetPathNodeCoordinates(int NodeNumber, bool BestZ = true); - bool CheckLosFN(VERTEX a, VERTEX b); + Map::Vertex GetPathNodeCoordinates(int NodeNumber, bool BestZ = true); + bool CheckLosFN(Map::Vertex a, Map::Vertex b); void SpawnPathNodes(); void MeshTest(); void SimpleMeshTest(); - int FindNearestPathNode(VERTEX Position); - bool NoHazards(VERTEX From, VERTEX To); - bool NoHazardsAccurate(VERTEX From, VERTEX To); + int FindNearestPathNode(Map::Vertex Position); + bool NoHazards(Map::Vertex From, Map::Vertex To); + bool NoHazardsAccurate(Map::Vertex From, Map::Vertex To); void OpenDoors(int Node1, int Node2, Mob* ForWho); PathNode* FindPathNodeByCoordinates(float x, float y, float z); diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index e9257d5a0..f5a527008 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -234,9 +234,9 @@ void NPC::UpdateWaypoint(int wp_index) if(!RuleB(Watermap, CheckForWaterAtWaypoints) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(cur_wp_x, cur_wp_y, cur_wp_z))) { - VERTEX dest(cur_wp_x, cur_wp_y, cur_wp_z); + Map::Vertex dest(cur_wp_x, cur_wp_y, cur_wp_z); - float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr); + float newz = zone->zonemap->FindBestZ(dest, nullptr); if( (newz > -2000) && ABS(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaWaypoint)) cur_wp_z = newz + 1; @@ -564,9 +564,9 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b if(!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos))) { - VERTEX dest(x_pos, y_pos, z_pos); + Map::Vertex dest(x_pos, y_pos, z_pos); - float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr); + float newz = zone->zonemap->FindBestZ(dest, nullptr); mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos); @@ -693,9 +693,9 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b if(!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos))) { - VERTEX dest(x_pos, y_pos, z_pos); + Map::Vertex dest(x_pos, y_pos, z_pos); - float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr); + float newz = zone->zonemap->FindBestZ(dest, nullptr); mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos); @@ -818,9 +818,9 @@ bool Mob::CalculateNewPosition(float x, float y, float z, float speed, bool chec if(!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos))) { - VERTEX dest(x_pos, y_pos, z_pos); + Map::Vertex dest(x_pos, y_pos, z_pos); - float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr); + float newz = zone->zonemap->FindBestZ(dest, nullptr); mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos); @@ -926,9 +926,9 @@ void NPC::AssignWaypoints(int32 grid) { if(!RuleB(Watermap, CheckWaypointsInWaterWhenLoading) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(newwp.x, newwp.y, newwp.z))) { - VERTEX dest(newwp.x, newwp.y, newwp.z); + Map::Vertex dest(newwp.x, newwp.y, newwp.z); - float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr); + float newz = zone->zonemap->FindBestZ(dest, nullptr); if( (newz > -2000) && ABS(newz-dest.z) < RuleR(Map, FixPathingZMaxDeltaLoading)) newwp.z = newz + 1; @@ -981,9 +981,9 @@ void Mob::SendTo(float new_x, float new_y, float new_z) { if(!RuleB(Watermap, CheckForWaterOnSendTo) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos))) { - VERTEX dest(x_pos, y_pos, z_pos); + Map::Vertex dest(x_pos, y_pos, z_pos); - float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr); + float newz = zone->zonemap->FindBestZ(dest, nullptr); mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos); @@ -1012,9 +1012,9 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { if(!RuleB(Watermap, CheckForWaterOnSendTo) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos))) { - VERTEX dest(x_pos, y_pos, z_pos); + Map::Vertex dest(x_pos, y_pos, z_pos); - float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr); + float newz = zone->zonemap->FindBestZ(dest, nullptr); mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos); diff --git a/zone/zone.cpp b/zone/zone.cpp index 487bda58f..a0fcb7414 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -103,7 +103,7 @@ bool Zone::Bootup(uint32 iZoneID, uint32 iInstanceID, bool iStaticZone) { worldserver.SetZone(0); return false; } - zone->zonemap = Map::LoadMapfile(zone->map_name); + zone->zonemap = Map::LoadMapFile(zone->map_name); zone->watermap = WaterMap::LoadWaterMapfile(zone->map_name); zone->pathing = PathManager::LoadPathFile(zone->map_name); From f6046477b407c7bc777a8bd9af5609769d1751c3 Mon Sep 17 00:00:00 2001 From: Corysia Taware Date: Thu, 3 Apr 2014 14:10:03 -0700 Subject: [PATCH 03/37] Changes for intel Mac OSX build --- CMakeLists.txt | 4 ++++ client_files/export/CMakeLists.txt | 4 +++- client_files/import/CMakeLists.txt | 4 +++- common/TCPConnection.cpp | 4 ++++ eqlaunch/CMakeLists.txt | 4 +++- loginserver/CMakeLists.txt | 4 +++- queryserv/CMakeLists.txt | 4 +++- shared_memory/CMakeLists.txt | 4 +++- tests/CMakeLists.txt | 4 +++- ucs/CMakeLists.txt | 4 +++- world/CMakeLists.txt | 4 +++- world/net.cpp | 2 +- zone/CMakeLists.txt | 4 +++- 13 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e39033656..e567b1de5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,10 @@ IF(UNIX) ADD_DEFINITIONS(-DFREEBSD) SET(FREEBSD TRUE) ENDIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + IF(CMAKE_SYSTEM_NAME MATCHES "Darwin") + ADD_DEFINITIONS(-DDARWIN) + SET(DARWIN TRUE) + ENDIF(CMAKE_SYSTEM_NAME MATCHES "Darwin") ENDIF(UNIX) #use stdint.h types if they exist for this platform (we have to guess otherwise) diff --git a/client_files/export/CMakeLists.txt b/client_files/export/CMakeLists.txt index 191184013..851aa05fb 100644 --- a/client_files/export/CMakeLists.txt +++ b/client_files/export/CMakeLists.txt @@ -26,7 +26,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(export_client_files "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(export_client_files "z") TARGET_LINK_LIBRARIES(export_client_files "m") - TARGET_LINK_LIBRARIES(export_client_files "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(export_client_files "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(export_client_files "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) diff --git a/client_files/import/CMakeLists.txt b/client_files/import/CMakeLists.txt index 94875302d..0b6c45b57 100644 --- a/client_files/import/CMakeLists.txt +++ b/client_files/import/CMakeLists.txt @@ -26,7 +26,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(import_client_files "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(import_client_files "z") TARGET_LINK_LIBRARIES(import_client_files "m") - TARGET_LINK_LIBRARIES(import_client_files "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(import_client_files "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(import_client_files "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) diff --git a/common/TCPConnection.cpp b/common/TCPConnection.cpp index e576f953a..1e5f49600 100644 --- a/common/TCPConnection.cpp +++ b/common/TCPConnection.cpp @@ -30,6 +30,10 @@ #ifdef FREEBSD //Timothy Whitman - January 7, 2003 #define MSG_NOSIGNAL 0 #endif +#ifdef DARWIN + #define MSG_NOSIGNAL SO_NOSIGPIPE // Corysia Taware - Sept. 27, 2013 + // See http://lists.apple.com/archives/macnetworkprog/2002/Dec/msg00091.html +#endif // DARWIN #ifdef _WINDOWS InitWinsock winsock; diff --git a/eqlaunch/CMakeLists.txt b/eqlaunch/CMakeLists.txt index 0f0114c88..b636a18cf 100644 --- a/eqlaunch/CMakeLists.txt +++ b/eqlaunch/CMakeLists.txt @@ -30,7 +30,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(eqlaunch "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(eqlaunch "z") TARGET_LINK_LIBRARIES(eqlaunch "m") - TARGET_LINK_LIBRARIES(eqlaunch "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(eqlaunch "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(eqlaunch "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) diff --git a/loginserver/CMakeLists.txt b/loginserver/CMakeLists.txt index ba96b1f61..9ded859e5 100644 --- a/loginserver/CMakeLists.txt +++ b/loginserver/CMakeLists.txt @@ -58,7 +58,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(loginserver "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(loginserver "z") TARGET_LINK_LIBRARIES(loginserver "m") - TARGET_LINK_LIBRARIES(loginserver "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(loginserver "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(loginserver "pthread") TARGET_LINK_LIBRARIES(loginserver "EQEmuAuthCrypto") TARGET_LINK_LIBRARIES(loginserver "cryptopp") diff --git a/queryserv/CMakeLists.txt b/queryserv/CMakeLists.txt index a8480ca94..33be865e0 100644 --- a/queryserv/CMakeLists.txt +++ b/queryserv/CMakeLists.txt @@ -36,7 +36,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(queryserv "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(queryserv "z") TARGET_LINK_LIBRARIES(queryserv "m") - TARGET_LINK_LIBRARIES(queryserv "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(queryserv "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(queryserv "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) diff --git a/shared_memory/CMakeLists.txt b/shared_memory/CMakeLists.txt index 863b5633f..3d23a1b01 100644 --- a/shared_memory/CMakeLists.txt +++ b/shared_memory/CMakeLists.txt @@ -38,7 +38,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(shared_memory "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(shared_memory "z") TARGET_LINK_LIBRARIES(shared_memory "m") - TARGET_LINK_LIBRARIES(shared_memory "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(shared_memory "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(shared_memory "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 80f6a2f1d..aef5124c9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,7 +32,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(tests "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(tests "z") TARGET_LINK_LIBRARIES(tests "m") - TARGET_LINK_LIBRARIES(tests "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(loginserver "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(tests "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) diff --git a/ucs/CMakeLists.txt b/ucs/CMakeLists.txt index 27a8a072d..44681831c 100644 --- a/ucs/CMakeLists.txt +++ b/ucs/CMakeLists.txt @@ -38,7 +38,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(ucs "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(ucs "z") TARGET_LINK_LIBRARIES(ucs "m") - TARGET_LINK_LIBRARIES(ucs "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(ucs "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(ucs "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) diff --git a/world/CMakeLists.txt b/world/CMakeLists.txt index 2decbf4c9..3ac008267 100644 --- a/world/CMakeLists.txt +++ b/world/CMakeLists.txt @@ -84,7 +84,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(world "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(world "z") TARGET_LINK_LIBRARIES(world "m") - TARGET_LINK_LIBRARIES(world "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(world "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(world "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) diff --git a/world/net.cpp b/world/net.cpp index 057367b00..8be3283f0 100644 --- a/world/net.cpp +++ b/world/net.cpp @@ -56,7 +56,7 @@ #include #include #include - #ifndef FREEBSD + #if not defined (FREEBSD) && not defined (DARWIN) union semun { int val; struct semid_ds *buf; diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 49410f0f2..e582e0c45 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -230,7 +230,9 @@ IF(UNIX) TARGET_LINK_LIBRARIES(zone "${CMAKE_DL_LIBS}") TARGET_LINK_LIBRARIES(zone "z") TARGET_LINK_LIBRARIES(zone "m") - TARGET_LINK_LIBRARIES(zone "rt") + IF(NOT DARWIN) + TARGET_LINK_LIBRARIES(zone "rt") + ENDIF(NOT DARWIN) TARGET_LINK_LIBRARIES(zone "pthread") ADD_DEFINITIONS(-fPIC) ENDIF(UNIX) From 0f67e93a0fd26b2ee50a920dbb5559db41ea0294 Mon Sep 17 00:00:00 2001 From: Corysia Taware Date: Sat, 5 Apr 2014 10:24:28 -0700 Subject: [PATCH 04/37] Backward port to OSX 10.6 (Snow Leopard) --- common/ipc_mutex.cpp | 11 +++++++++-- zone/client_packet.cpp | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/common/ipc_mutex.cpp b/common/ipc_mutex.cpp index 1ac88538c..331144eca 100644 --- a/common/ipc_mutex.cpp +++ b/common/ipc_mutex.cpp @@ -55,10 +55,17 @@ namespace EQEmu { std::string final_name = name; final_name += ".lock"; +#ifdef __DARWIN +#if __DARWIN_C_LEVEL < 200809L imp_->fd_ = open(final_name.c_str(), - O_RDWR | O_CREAT | O_CLOEXEC, + O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); - +#else + imp_->fd_ = open(final_name.c_str(), + O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR); +#endif +#endif if(imp_->fd_ == -1) { EQ_EXCEPT("IPC Mutex", "Could not create mutex."); } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index cde2b143f..0a50d8cdc 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -12483,7 +12483,15 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app) // char *GuildName = (char *)app->pBuffer; +#ifdef DARWIN +#if __DARWIN_C_LEVEL < 200809L + if (strlen(GuildName) > 60) +#else if(strnlen(GuildName, 64) > 60) +#endif // __DARWIN_C_LEVEL +#else + if(strnlen(GuildName, 64) > 60) +#endif // DARWIN { Message(clientMessageError, "Guild name too long."); return; @@ -12941,7 +12949,15 @@ void Client::Handle_OP_LFGuild(const EQApplicationPacket *app) VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_PlayerToggle_Struct); LFGuild_PlayerToggle_Struct *pts = (LFGuild_PlayerToggle_Struct *)app->pBuffer; +#ifdef DARWIN +#if __DARWIN_C_LEVEL < 200809L + if (strlen(pts->Comment) > 256) +#else if(strnlen(pts->Comment, 256) > 256) +#endif // __DARWIN_C_LEVEL +#else + if(strnlen(pts->Comment, 256) > 256) +#endif // DARWIN return; ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + strlen(pts->Comment) + 38); @@ -12968,7 +12984,15 @@ void Client::Handle_OP_LFGuild(const EQApplicationPacket *app) VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_GuildToggle_Struct); LFGuild_GuildToggle_Struct *gts = (LFGuild_GuildToggle_Struct *)app->pBuffer; - if(strnlen(gts->Comment, 256) > 256) +#ifdef DARWIN +#if __DARWIN_C_LEVEL < 200809L + if (strlen(gts->Comment) > 256) +#else + if(strnlen(gts->Comment, 256) > 256) +#endif // __DARWIN_C_LEVEL +#else + if(strnlen(gts->Comment, 256) > 256) +#endif // __DARWIN return; ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + strlen(gts->Comment) + strlen(guild_mgr.GetGuildName(GuildID())) + 43); From 35cd98c7a763f62d22fa7d0e2f9029ef3a168e1a Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Wed, 9 Apr 2014 05:17:36 -0400 Subject: [PATCH 05/37] -Implemented the ability to properly use live spell projectile graphics. This data is found in the player_1 field of the spells_new table. -Rule for this set to be disabled by default. -Enable IF your server uses an UF+ spell file and your players use UF+ clients -Otherwise your better off with alternative method/rules already implemented so that all players can see the effect. -Added ability for PERL ProjectileAnim function to only need an IT#### and not an actual item id. -If you want it in LUA somebody needs to add it. - Change to wizard innate critical ratios based on parse data. --- changelog.txt | 12 ++++- common/ruletypes.h | 1 + .../2014_04_03_SpellProjectileRules.sql | 4 +- .../2014_04_09_SpellProjectileRule.sql | 7 +++ zone/effects.cpp | 4 +- zone/mob.h | 2 +- zone/perl_mob.cpp | 9 ++-- zone/special_attacks.cpp | 9 ++-- zone/spell_effects.cpp | 50 +++++++++++-------- 9 files changed, 64 insertions(+), 34 deletions(-) create mode 100644 utils/sql/git/optional/2014_04_09_SpellProjectileRule.sql diff --git a/changelog.txt b/changelog.txt index dafc65268..08f8df3c4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,10 +1,20 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/09/2014 == +Kayen: Implemented ability to use the actual live spell projectile graphics that are defined in the modern spell file. + *This is disabled by default. Recommend enabling if your server uses an UF+ spell file AND most of your players use UF+ clients. +Kayen: Expanded the PERL ProjectileAnim(mob, item_id, [IsArrow?, speed, angle, tilt, arc, IDFile]) so you can now just set the weapon model graphic IT#### +Example: ProjectileAnim($npc, 0, 0, 1, 0, 0, 0, "IT10747") This will shoot an SK 2.0 sword. +Kayen: Updated wizard innate critical damage modifier to be from 20-70% of base damage (based on live parses) + + +Optional SQL: utils/sql/git/optional/2014_04_09_SpellProjectileRule.sql +Note: This sql also contains a query to check if your spell file is compatible. + == 04/06/2014 == Uleat: Changed Mob::CanThisClassDualWield() behavior. This should let non-monk/beastlord dual-wielding classes attack with either fist as long as the other hand is occupied. Notes: See this thread for more information and to provide feedback: http://www.eqemulator.org/forums/showthread.php?p=229328#post229328 - == 04/05/2014 == Akkadius: Fix for the Fix for the Fix: Rule Combat:OneProcPerWeapon was created so that you can revert to the original proc functionality diff --git a/common/ruletypes.h b/common/ruletypes.h index dec5a6a8f..5612a90c0 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -307,6 +307,7 @@ RULE_INT ( Spells, SuccorFailChance, 2) //Determines chance for a succor spell n RULE_INT ( Spells, FRProjectileItem_Titanium, 1113) // Item id for Titanium clients for Fire 'spell projectile'. RULE_INT ( Spells, FRProjectileItem_SOF, 80684) // Item id for SOF clients for Fire 'spell projectile'. RULE_INT ( Spells, FRProjectileItem_NPC, 80684) // Item id for NPC Fire 'spell projectile'. +RULE_BOOL ( Spells, UseLiveSpellProjectileGFX, false) // Use spell projectile graphics set in the spells_new table (player_1). Server must be using UF+ spell file. RULE_CATEGORY_END() RULE_CATEGORY( Combat ) diff --git a/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql b/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql index 3601a5fef..01950cc1a 100644 --- a/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql +++ b/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql @@ -1,3 +1,3 @@ INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_Titanium', '1113', 'Item id for Titanium clients for Fire spell projectile.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_SOF', '80684', 'Item id for Titanium clients for Fire spell projectile.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_NPC', '80684', 'Item id for Titanium clients for Fire spell projectile.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_SOF', '80684', 'Item id for SOF clients for Fire spell projectile.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_NPC', '80684', 'Item id for NPC to use for Fire spell projectile.'); diff --git a/utils/sql/git/optional/2014_04_09_SpellProjectileRule.sql b/utils/sql/git/optional/2014_04_09_SpellProjectileRule.sql new file mode 100644 index 000000000..1a4c9352e --- /dev/null +++ b/utils/sql/git/optional/2014_04_09_SpellProjectileRule.sql @@ -0,0 +1,7 @@ +-- Recommend enabling if your server uses an UF+ spell file and your players use UF+ client. This will give the proper graphics for all spell projectiles. +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:UseLiveSpellProjectileGFX', false, ' Use spell projectile graphics set in the spells_new table (player_1). Server must be using UF+ spell file.'); + +-- Use this query to check if your spell file is compatible +-- If it returns in the player_1 field IT##### it will work. +SELECT id,name,player_1 from spells_new WHERE targettype = 1; + diff --git a/zone/effects.cpp b/zone/effects.cpp index 48e30639c..329c69084 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -79,7 +79,7 @@ int32 Client::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { if (spell_id == SPELL_IMP_HARM_TOUCH) //Improved Harm Touch value -= GetAA(aaUnholyTouch) * 450; //Unholy Touch - int chance = RuleI(Spells, BaseCritChance); + int chance = RuleI(Spells, BaseCritChance); //Wizard base critical chance is 2% (Does not scale with level) chance += itembonuses.CriticalSpellChance + spellbonuses.CriticalSpellChance + aabonuses.CriticalSpellChance; chance += itembonuses.FrenziedDevastation + spellbonuses.FrenziedDevastation + aabonuses.FrenziedDevastation; @@ -99,7 +99,7 @@ int32 Client::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { } else if (GetClass() == WIZARD && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (MakeRandomInt(1,100) <= RuleI(Spells, WizCritChance))) { - ratio += MakeRandomInt(1,100); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. + ratio += MakeRandomInt(20,70); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. (20-70 is parse confirmed) Critical = true; } diff --git a/zone/mob.h b/zone/mob.h index 4ee5da5a8..a5ce86903 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -161,7 +161,7 @@ public: virtual void WearChange(uint8 material_slot, uint16 texture, uint32 color); void DoAnim(const int animnum, int type=0, bool ackreq = true, eqFilterType filter = FilterNone); void ProjectileAnimation(Mob* to, int item_id, bool IsArrow = false, float speed = 0, - float angle = 0, float tilt = 0, float arc = 0); + float angle = 0, float tilt = 0, float arc = 0, const char *IDFile = nullptr); void ChangeSize(float in_size, bool bNoRestriction = false); inline uint8 SeeInvisible() const { return see_invis; } inline bool SeeInvisibleUndead() const { return see_invis_undead; } diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index a2958db27..86cf58981 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -6856,7 +6856,7 @@ XS(XS_Mob_ProjectileAnim); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_ProjectileAnim) { dXSARGS; - if (items < 3 || items > 8) + if (items < 3 || items > 9) Perl_croak(aTHX_ "Usage: Mob::ProjectileAnim(THIS, mob, item_id, IsArrow?, speed, angle, tilt, arc)"); { @@ -6868,6 +6868,7 @@ XS(XS_Mob_ProjectileAnim) float angle = 0; float tilt = 0; float arc = 0; + char * IDFile = nullptr; if (sv_derived_from(ST(0), "Mob")) { IV tmp = SvIV((SV*)SvRV(ST(0))); @@ -6903,7 +6904,9 @@ XS(XS_Mob_ProjectileAnim) arc = (float)SvNV(ST(7)); } - THIS->ProjectileAnimation(mob, item_id, IsArrow, speed, angle, tilt, arc); + if (items > 8) { IDFile = (char *)SvPV_nolen(ST(8)); } + + THIS->ProjectileAnimation(mob, item_id, IsArrow, speed, angle, tilt, arc, IDFile); } XSRETURN_EMPTY; @@ -8389,7 +8392,7 @@ XS(boot_Mob) newXSproto(strcpy(buf, "CheckLoS"), XS_Mob_CheckLoS, file, "$$"); newXSproto(strcpy(buf, "CheckLoSToLoc"), XS_Mob_CheckLoSToLoc, file, "$$$$;$"); newXSproto(strcpy(buf, "FindGroundZ"), XS_Mob_FindGroundZ, file, "$$$;$"); - newXSproto(strcpy(buf, "ProjectileAnim"), XS_Mob_ProjectileAnim, file, "$$$;$$$$$"); + newXSproto(strcpy(buf, "ProjectileAnim"), XS_Mob_ProjectileAnim, file, "$$$;$$$$$$"); newXSproto(strcpy(buf, "HasNPCSpecialAtk"), XS_Mob_HasNPCSpecialAtk, file, "$$"); newXSproto(strcpy(buf, "SendAppearanceEffect"), XS_Mob_SendAppearanceEffect, file, "$$;$$$$"); newXSproto(strcpy(buf, "SetFlyMode"), XS_Mob_SetFlyMode, file, "$$"); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 8d95524fe..54e70f3d0 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -1346,7 +1346,7 @@ void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skil safe_delete(outapp); } -void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc) { +void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc, const char *IDFile) { const Item_Struct* item = nullptr; uint8 item_type = 0; @@ -1380,6 +1380,10 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f arc = 50; } + const char *item_IDFile = item->IDFile; + + if (IDFile && (strncmp(IDFile, "IT", 2) == 0)) + item_IDFile = IDFile; // See SendItemAnimation() for some notes on this struct EQApplicationPacket *outapp = new EQApplicationPacket(OP_SomeItemPacketMaybe, sizeof(Arrow_Struct)); @@ -1393,7 +1397,7 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f as->item_id = item->ID; as->item_type = item_type; as->skill = 0; // Doesn't seem to have any effect - strn0cpy(as->model_name, item->IDFile, 16); + strn0cpy(as->model_name, item_IDFile, 16); as->velocity = speed; as->launch_angle = angle; as->tilt = tilt; @@ -1406,7 +1410,6 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f } - void NPC::DoClassAttacks(Mob *target) { if(target == nullptr) return; //gotta have a target for all these diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index ce299ed36..ad7e273fd 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -3351,10 +3351,7 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste case SE_Root: { /* Root formula derived from extensive personal live parses - Kayen - ROOT has a 40% chance to do a resist check to break. - Resist check has NO LOWER bounds. - If multiple roots on target. Root in first slot will be checked first to break from nukes. - If multiple roots on target and broken by spell. Roots are removed ONE at a time in order of buff slot. + ROOT has a 70% chance to do a resist check to break. */ if (MakeRandomInt(0, 99) < RuleI(Spells, RootBreakCheckChance)){ @@ -6064,26 +6061,35 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){ projectile_timer.Start(250); } - //Only use fire graphic for fire spells. - if (spells[spell_id].resisttype == RESIST_FIRE) { - - if (IsClient()){ - if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic. - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5); - else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5); - } - - else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5); - - if (spells[spell_id].CastingAnim == 64) - anim = 44; //Corrects for animation error. + //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, 1.5,0,0,0, spells[spell_id].player_1); } - //Pending other types of projectile graphics. (They will function but with a default arrow graphic for now) - else - ProjectileAnimation(spell_target,0, 1, 1.5); + //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 (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic. + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5); + else + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5); + } + + else + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5); + + } + + //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, 1.5); + } + + if (spells[spell_id].CastingAnim == 64) + anim = 44; //Corrects for animation error. DoAnim(anim, 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); //Override the default projectile animation. return true; From f0a0f0677f4a32d16f34e2aa1518e57f49fa2acd Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 10 Apr 2014 00:47:20 -0400 Subject: [PATCH 06/37] Rule to make player cast swarm pets untargetable with F8. New npc_types field to allow any NPC to be untargetable with F8 Swarm pets will now be heal/buffable like live. See change log for more details. Note: The method used here to prevent targeting is a hack but the only side effect it turns affected NPC's names Yellow. --- changelog.txt | 9 +++++++++ common/ruletypes.h | 1 + zone/mob.cpp | 5 +++-- zone/mob.h | 3 ++- zone/npc.cpp | 24 ++++++++++++++++++++++++ zone/npc.h | 1 + zone/zonedb.cpp | 8 +++++--- zone/zonedump.h | 1 + 8 files changed, 46 insertions(+), 6 deletions(-) diff --git a/changelog.txt b/changelog.txt index 08f8df3c4..d21af0a8b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,14 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/10/2014 == +Kayen: Added 'no_target_hotkey' field to npc_types table. This will prevent the NPC from being targeted with F8 (Warning: Will also turn it's name YELLOW) +Kayen: Added a rule to make all (Player cast) Swarm Pets not targetable with F8 (nearest NPC) by default (Warning: Will also turn pets names YELLOW). This is semi-hack but it works. +Kayen: Player cast swarm pets can now be healed and buffed consistent with live. + +Optional SQL: utils/sql/git/optional/2014_04_10_SwarmPetNotTargetableWithHotKey.sql +Required SQL: utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql +Note: For the required new npc_types field you DO NOT need to set values for swarm pets if you enable the above rule. + == 04/09/2014 == Kayen: Implemented ability to use the actual live spell projectile graphics that are defined in the modern spell file. *This is disabled by default. Recommend enabling if your server uses an UF+ spell file AND most of your players use UF+ clients. diff --git a/common/ruletypes.h b/common/ruletypes.h index 5612a90c0..fa07c752e 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -135,6 +135,7 @@ RULE_CATEGORY_END() RULE_CATEGORY( Pets ) RULE_REAL( Pets, AttackCommandRange, 150 ) RULE_BOOL( Pets, UnTargetableSwarmPet, false ) +RULE_BOOL( Pets, SwarmPetNotTargetableWithHotKey, false ) //On SOF+ clients this a semi-hack to make swarm pets not F8 targetable. RULE_CATEGORY_END() RULE_CATEGORY( GM ) diff --git a/zone/mob.cpp b/zone/mob.cpp index 87486714c..2538a3a49 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -891,7 +891,8 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) ns->spawn.invis = (invisible || hidden) ? 1 : 0; // TODO: load this before spawning players ns->spawn.NPC = IsClient() ? 0 : 1; - ns->spawn.IsMercenary = IsMerc() ? 1 : 0; + ns->spawn.IsMercenary = (IsMerc() || no_target_hotkey) ? 1 : 0; + ns->spawn.petOwnerId = ownerid; ns->spawn.haircolor = haircolor; @@ -1626,7 +1627,7 @@ void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 la->parm4 = parm4; la->parm5 = parm5; // Note that setting the b values to 0 will disable the related effect from the corresponding parameter. - // Setting the a value appears to have no affect at all. + // Setting the a value appears to have no affect at all.s la->value1a = 1; la->value1b = 1; la->value2a = 1; diff --git a/zone/mob.h b/zone/mob.h index a5ce86903..bf3399251 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -170,7 +170,7 @@ public: bool IsInvisible(Mob* other = 0) const; void SetInvisible(uint8 state); bool AttackAnimation(SkillUseTypes &skillinuse, int Hand, const ItemInst* weapon); - + //Song bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1); bool ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, uint16 slot); @@ -945,6 +945,7 @@ protected: int16 petpower; uint32 follow; uint32 follow_dist; + bool no_target_hotkey; uint8 gender; uint16 race; diff --git a/zone/npc.cpp b/zone/npc.cpp index f83d1d31b..20bc025fc 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -224,6 +224,8 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float p_depop = false; loottable_id = d->loottable_id; + no_target_hotkey = d->no_target_hotkey; + primary_faction = 0; SetNPCFactionID(d->npc_faction_id); @@ -1719,6 +1721,22 @@ bool Mob::HasNPCSpecialAtk(const char* parse) { void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { Mob::FillSpawnStruct(ns, ForWho); + + //Basic settings to make sure swarm pets work properly. + if (GetSwarmOwner()) { + Client *c = entity_list.GetClientByID(GetSwarmOwner()); + if(c) { + SetAllowBeneficial(1); //Allow client cast swarm pets to be heal/buffed. + //This is a hack to allow CLIENT swarm pets NOT to be targeted with F8. Warning: Will turn name 'Yellow'! + if (RuleB(Pets, SwarmPetNotTargetableWithHotKey)) + ns->spawn.IsMercenary = 1; + } + //NPC cast swarm pets should still be targetable with F8. + else + ns->spawn.IsMercenary = 0; + } + + //Not recommended if using above (However, this will work better on older clients). if (RuleB(Pets, UnTargetableSwarmPet)) { if(GetOwnerID() || GetSwarmOwner()) { ns->spawn.is_pet = 1; @@ -1867,6 +1885,12 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) return; } + if(id == "PhR") + { + PhR = atoi(val.c_str()); + return; + } + if(id == "runspeed") { runspeed = (float)atof(val.c_str()); diff --git a/zone/npc.h b/zone/npc.h index e8877172b..406be550f 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -245,6 +245,7 @@ public: void AddLootDrop(const Item_Struct*dbitem, ItemList* itemlistconst, int16 charges, uint8 minlevel, uint8 maxlevel, bool equipit, bool wearchange = false); virtual void DoClassAttacks(Mob *target); void CheckSignal(); + inline bool IsTargetableWithHotkey() const { return no_target_hotkey; } //waypoint crap int GetMaxWp() const { return max_wp; } diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 568780e7d..119d83cc2 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1099,7 +1099,8 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { "npc_types.underwater," "npc_types.emoteid," "npc_types.spellscale," - "npc_types.healscale"; + "npc_types.healscale," + "npc_types.no_target_hotkey"; MakeAnyLenString(&query, "%s FROM npc_types WHERE id=%d", basic_query, id); @@ -1191,7 +1192,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->armor_tint[0] |= (atoi(row[r++]) & 0xFF) << 8; tmpNPCType->armor_tint[0] |= (atoi(row[r++]) & 0xFF); tmpNPCType->armor_tint[0] |= (tmpNPCType->armor_tint[0]) ? (0xFF << 24) : 0; - + int i; if (armor_tint_id > 0) { @@ -1281,7 +1282,8 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->emoteid = atoi(row[r++]); tmpNPCType->spellscale = atoi(row[r++]); tmpNPCType->healscale = atoi(row[r++]); - + tmpNPCType->no_target_hotkey = atoi(row[r++]) == 1 ? true : false; + // If NPC with duplicate NPC id already in table, // free item we attempted to add. if (zone->npctable.find(tmpNPCType->npc_id) != zone->npctable.end()) diff --git a/zone/zonedump.h b/zone/zonedump.h index 2c036be61..ab8f88d23 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -120,6 +120,7 @@ struct NPCType uint32 emoteid; float spellscale; float healscale; + bool no_target_hotkey; }; /* From 48a9f05efbd258ae0e39164b72b9331ef684f0c3 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 10 Apr 2014 00:55:31 -0400 Subject: [PATCH 07/37] SQL --- .../optional/2014_04_10_SwarmPetNotTargetableWithHotKey.sql | 4 ++++ utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 utils/sql/git/optional/2014_04_10_SwarmPetNotTargetableWithHotKey.sql create mode 100644 utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql diff --git a/utils/sql/git/optional/2014_04_10_SwarmPetNotTargetableWithHotKey.sql b/utils/sql/git/optional/2014_04_10_SwarmPetNotTargetableWithHotKey.sql new file mode 100644 index 000000000..25274e53b --- /dev/null +++ b/utils/sql/git/optional/2014_04_10_SwarmPetNotTargetableWithHotKey.sql @@ -0,0 +1,4 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Pets:SwarmPetNotTargetableWithHotKey', 'false', ' On SOF+ clients this a semi-hack to make swarm pets not F8 targetable. Warning: Turns pet names Yellow'); + + + diff --git a/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql b/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql new file mode 100644 index 000000000..12e9963a5 --- /dev/null +++ b/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql @@ -0,0 +1,3 @@ +ALTER TABLE `npc_types` ADD `no_target_hotkey` tinyint( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `healscale`; + + From f9b46b46b1a1d83ce9ad3abba139c782fe127d5f Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sat, 12 Apr 2014 00:18:19 -0400 Subject: [PATCH 08/37] Revision of slow mitigation code. Fix for the slow mitigation spam messages Converted value from FLOAT to INT Use SQL to update your npc_types table --- changelog.txt | 8 +++++ .../required/2014_04_12_SlowMitigation.sql | 7 +++++ zone/StringIDs.h | 14 +++++---- zone/bonuses.cpp | 30 +++++-------------- zone/lua_npc.cpp | 4 +-- zone/mob.cpp | 27 +++++++++++++++-- zone/mob.h | 5 ++-- zone/npc.cpp | 2 +- zone/npc.h | 2 +- zone/perl_npc.cpp | 4 +-- zone/spell_effects.cpp | 27 ++++++++++++++--- zone/zonedb.cpp | 2 +- zone/zonedump.h | 2 +- 13 files changed, 90 insertions(+), 44 deletions(-) create mode 100644 utils/sql/git/required/2014_04_12_SlowMitigation.sql diff --git a/changelog.txt b/changelog.txt index d21af0a8b..083250403 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,13 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- + +== 04/12/2014 == +Kayen: Fixed an with the slow mitigation code that would cause it to spam the message. Optimized the way the variable is handled for slow mitigation. + +Required SQL: utils/sql/git/required/2014_04_12_SlowMitigation.sql +Note: This changes the variable type in the sql table from FLOAT to INT and updates your database. +(When setting slow mitigation 50 = 50%, 100 = 100% ect. You can also set > 100 which will cause slow to become haste now with appropriate in game msg given) + == 04/10/2014 == Kayen: Added 'no_target_hotkey' field to npc_types table. This will prevent the NPC from being targeted with F8 (Warning: Will also turn it's name YELLOW) Kayen: Added a rule to make all (Player cast) Swarm Pets not targetable with F8 (nearest NPC) by default (Warning: Will also turn pets names YELLOW). This is semi-hack but it works. diff --git a/utils/sql/git/required/2014_04_12_SlowMitigation.sql b/utils/sql/git/required/2014_04_12_SlowMitigation.sql new file mode 100644 index 000000000..f2ac0d68d --- /dev/null +++ b/utils/sql/git/required/2014_04_12_SlowMitigation.sql @@ -0,0 +1,7 @@ +-- Convert all values from FLOAT to INT +UPDATE npc_types SET slow_mitigation = slow_mitigation * 100; + +-- Change variable type from FLOAT TO INT +ALTER TABLE npc_types MODIFY slow_mitigation smallint(4); + + diff --git a/zone/StringIDs.h b/zone/StringIDs.h index 05bbffdec..c916b1de0 100644 --- a/zone/StringIDs.h +++ b/zone/StringIDs.h @@ -243,12 +243,12 @@ #define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1! #define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end. #define REWIND_WAIT 4059 //You must wait a bit longer before using the rewind command again. -#define CORPSEDRAG_LIMIT 4061 //You are already dragging as much as you can! -#define CORPSEDRAG_ALREADY 4062 //You are already dragging %1. +#define CORPSEDRAG_LIMIT 4061 //You are already dragging as much as you can! +#define CORPSEDRAG_ALREADY 4062 //You are already dragging %1. #define CORPSEDRAG_SOMEONE_ELSE 4063 //Someone else is dragging %1. -#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 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 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. @@ -300,6 +300,10 @@ #define GAIN_RAID_LEADERSHIP_EXP 8789 // #define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining) #define FEAR_TOO_HIGH 9035 //Your target is too high of a level for your fear spell. +#define SLOW_MOSTLY_SUCCESSFUL 9029 //Your spell was mostly successful. +#define SLOW_PARTIALLY_SUCCESSFUL 9030 // Your spell was partially successful. +#define SLOW_SLIGHTLY_SUCCESSFUL 9031 //Your spell was slightly successful. +#define SPELL_OPPOSITE_EFFECT 9032 //Your spell may have had the opposite effect of what you desired. #define YOU_HEAL 9068 //You have healed %1 for %2 points of damage. #define YOUR_HIT_DOT 9072 //%1 has taken %2 damage from your %3. #define HIT_NON_MELEE 9073 //%1 hit %2 for %3 points of non-melee damage. diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 56f8b9f5f..c726333f2 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1338,19 +1338,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne } } else if ((effect_value - 100) < 0) { // Slow - //Slow Mitigation works by taking the amount that would be slowed, and adding a multiplied version of the difference. int real_slow_value = (100 - effect_value) * -1; - if (slow_mitigation){ - int new_effect_value = SlowMitigation(false,caster,real_slow_value); - if (new_effect_value < newbon->haste) { - newbon->haste = new_effect_value; - SlowMitigation(true,caster); - } - } - else { - if (real_slow_value < newbon->haste) - newbon->haste = real_slow_value; - } + real_slow_value -= ((real_slow_value * GetSlowMitigation()/100)); + if (real_slow_value < newbon->haste) + newbon->haste = real_slow_value; } break; } @@ -1365,6 +1356,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne } else if ((effect_value - 100) < 0) { // Slow int real_slow_value = (100 - effect_value) * -1; + real_slow_value -= ((real_slow_value * GetSlowMitigation()/100)); if (real_slow_value < newbon->hastetype2) newbon->hastetype2 = real_slow_value; } @@ -1374,6 +1366,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_AttackSpeed3: { if (effect_value < 0){ //Slow + effect_value -= ((effect_value * GetSlowMitigation()/100)); if (effect_value < newbon->hastetype3) newbon->hastetype3 = effect_value; } @@ -1392,18 +1385,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne effect_value = effect_value * -1; if (effect_value > 0 && effect_value > newbon->inhibitmelee) { - - if (slow_mitigation){ - int new_effect_value = SlowMitigation(false,caster,effect_value); - if (new_effect_value > newbon->inhibitmelee) { - newbon->inhibitmelee = new_effect_value; - SlowMitigation(true,caster); - } - } - - else if (effect_value > newbon->inhibitmelee) { + effect_value -= ((effect_value * GetSlowMitigation()/100)); + if (effect_value > newbon->inhibitmelee) newbon->inhibitmelee = effect_value; - } } break; diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index 1dd61cc91..41524af15 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -408,7 +408,7 @@ void Lua_NPC::SetSpellFocusHeal(int focus) { } float Lua_NPC::GetSlowMitigation() { - Lua_Safe_Call_Real(); + Lua_Safe_Call_Int(); return self->GetSlowMitigation(); } @@ -526,7 +526,7 @@ luabind::scope lua_register_npc() { .def("RemoveAISpell", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveAISpell) .def("SetSpellFocusDMG", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusDMG) .def("SetSpellFocusHeal", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusHeal) - .def("GetSlowMitigation", (float(Lua_NPC::*)(void))&Lua_NPC::GetSlowMitigation) + .def("GetSlowMitigation", (int(Lua_NPC::*)(void))&Lua_NPC::GetSlowMitigation) .def("GetAttackSpeed", (float(Lua_NPC::*)(void))&Lua_NPC::GetAttackSpeed) .def("GetAccuracyRating", (int(Lua_NPC::*)(void))&Lua_NPC::GetAccuracyRating) .def("GetSpawnKillCount", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnKillCount) diff --git a/zone/mob.cpp b/zone/mob.cpp index 2538a3a49..74c44c7cc 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4651,8 +4651,12 @@ void Mob::CastOnNumHitFade(uint32 spell_id) } } +/* int Mob::SlowMitigation(bool slow_msg, Mob *caster, int slow_value) { + if (slow_mitigation) + return 0; + float int_slow_mitigation = slow_mitigation * 100.0f; if (int_slow_mitigation > 100.0f) @@ -4663,10 +4667,10 @@ int Mob::SlowMitigation(bool slow_msg, Mob *caster, int slow_value) if (caster && caster->IsClient()) { if ((int_slow_mitigation > 0.0f) && (int_slow_mitigation < 26.0f)) - caster->Message(262, "Your spell was mostly successful"); + caster->Message_StringID(MT_Spells, SLOW_MOSTLY_SUCCESSFUL); else if ((int_slow_mitigation >= 26.0f) && (int_slow_mitigation < 74.0f)) - caster->Message(262, "Your spell was partially successful"); + caster->Message_StringID(MT_Spells, SLOW_PARTIALLY_SUCCESSFUL); else if ((int_slow_mitigation >= 74.0f) && (int_slow_mitigation < 101.0f)) caster->Message(262, "Your spell was slightly successful"); @@ -4680,6 +4684,25 @@ int Mob::SlowMitigation(bool slow_msg, Mob *caster, int slow_value) return slow_value; } } +*/ + +void Mob::SlowMitigation(Mob* caster) +{ + if (GetSlowMitigation() && caster && caster->IsClient()) + { + if ((GetSlowMitigation() > 0) && (GetSlowMitigation() < 26)) + caster->Message_StringID(MT_SpellFailure, SLOW_MOSTLY_SUCCESSFUL); + + else if ((GetSlowMitigation() >= 26) && (GetSlowMitigation() < 74)) + caster->Message_StringID(MT_SpellFailure, SLOW_PARTIALLY_SUCCESSFUL); + + else if ((GetSlowMitigation() >= 74) && (GetSlowMitigation() < 101)) + caster->Message_StringID(MT_SpellFailure, SLOW_SLIGHTLY_SUCCESSFUL); + + else if (GetSlowMitigation() > 100) + caster->Message_StringID(MT_SpellFailure, SPELL_OPPOSITE_EFFECT); + } +} uint16 Mob::GetSkillByItemType(int ItemType) { diff --git a/zone/mob.h b/zone/mob.h index bf3399251..d79f30867 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -580,7 +580,7 @@ public: void CastOnCurer(uint32 spell_id); void CastOnCure(uint32 spell_id); void CastOnNumHitFade(uint32 spell_id); - int SlowMitigation(bool slow_msg=false, Mob *caster = nullptr,int slow_value = 0); + void SlowMitigation(Mob* caster); int16 GetCritDmgMob(uint16 skill); int16 GetMeleeDamageMod_SE(uint16 skill); int16 GetMeleeMinDamageMod_SE(uint16 skill); @@ -598,6 +598,7 @@ public: bool PassCastRestriction(bool UseCastRestriction = true, int16 value = 0, bool IsDamage = true); bool ImprovedTaunt(); bool TryRootFadeByDamage(int buffslot, Mob* attacker); + int16 GetSlowMitigation() const {return slow_mitigation;} void ModSkillDmgTaken(SkillUseTypes skill_num, int value); int16 GetModSkillDmgTaken(const SkillUseTypes skill_num); @@ -1023,7 +1024,7 @@ protected: Timer attack_dw_timer; Timer ranged_timer; float attack_speed; //% increase/decrease in attack speed (not haste) - float slow_mitigation; // Allows for a slow mitigation based on a % in decimal form. IE, 1 = 100% mitigation, .5 is 50% + float slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%) Timer tic_timer; Timer mana_timer; diff --git a/zone/npc.cpp b/zone/npc.cpp index 20bc025fc..2611300d8 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -2004,7 +2004,7 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) if(id == "slow_mitigation") { - slow_mitigation = atof(val.c_str()); + slow_mitigation = atoi(val.c_str()); return; } if(id == "loottable_id") diff --git a/zone/npc.h b/zone/npc.h index 406be550f..55bed4abc 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -233,7 +233,7 @@ public: uint32 GetMaxDMG() const {return max_dmg;} uint32 GetMinDMG() const {return min_dmg;} - float GetSlowMitigation() const {return slow_mitigation;} + int16 GetSlowMitigation() const {return slow_mitigation;} float GetAttackSpeed() const {return attack_speed;} bool IsAnimal() const { return(bodytype == BT_Animal); } uint16 GetPetSpellID() const {return pet_spell_id;} diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index 2ab0ca036..891709113 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -2023,7 +2023,7 @@ XS(XS_NPC_GetSlowMitigation) Perl_croak(aTHX_ "Usage: NPC::GetSlowMitigation(THIS)"); { NPC * THIS; - float RETVAL; + int16 RETVAL; dXSTARG; if (sv_derived_from(ST(0), "NPC")) { @@ -2036,7 +2036,7 @@ XS(XS_NPC_GetSlowMitigation) Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); RETVAL = THIS->GetSlowMitigation(); - XSprePUSH; PUSHn((double)RETVAL); + XSprePUSH; PUSHn((UV)RETVAL); } XSRETURN(1); } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index ad7e273fd..61e220d2d 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2624,14 +2624,37 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) HealDamage(dmg, caster); } } + + break; } case SE_Taunt: { if (IsNPC()) caster->Taunt(this->CastToNPC(), false, spell.base[i]); + + break; } + case SE_AttackSpeed: + if (spell.base[i] < 100) + SlowMitigation(caster); + break; + + case SE_AttackSpeed2: + if (spell.base[i] < 100) + SlowMitigation(caster); + break; + + case SE_AttackSpeed3: + if (spell.base[i] < 0) + SlowMitigation(caster); + break; + + case SE_AttackSpeed4: + SlowMitigation(caster); + break; + // Handled Elsewhere case SE_ImmuneFleeing: case SE_NegateSpellEffect: @@ -2717,10 +2740,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) case SE_DivineSave: case SE_Accuracy: case SE_Flurry: - case SE_AttackSpeed: - case SE_AttackSpeed2: - case SE_AttackSpeed3: - case SE_AttackSpeed4: case SE_ImprovedDamage: case SE_ImprovedHeal: case SE_IncreaseSpellHaste: diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 119d83cc2..893a1a27a 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1273,7 +1273,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->see_improved_hide = atoi(row[r++])==0?false:true; tmpNPCType->ATK = atoi(row[r++]); tmpNPCType->accuracy_rating = atoi(row[r++]); - tmpNPCType->slow_mitigation = atof(row[r++]); + tmpNPCType->slow_mitigation = atoi(row[r++]); tmpNPCType->maxlevel = atoi(row[r++]); tmpNPCType->scalerate = atoi(row[r++]); tmpNPCType->private_corpse = atoi(row[r++]) == 1 ? true : false; diff --git a/zone/zonedump.h b/zone/zonedump.h index ab8f88d23..39c6b39d7 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -111,7 +111,7 @@ struct NPCType int accuracy_rating; //10 = 1% accuracy bool findable; //can be found with find command bool trackable; - float slow_mitigation; // Slow mitigation % in decimal form. + int16 slow_mitigation; uint8 maxlevel; uint32 scalerate; bool private_corpse; From aedd70f5faee97019ad735ed848295e54bb4120f Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sat, 12 Apr 2014 03:31:30 -0400 Subject: [PATCH 09/37] removed commented out code. --- zone/mob.cpp | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index 74c44c7cc..48452f26a 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4651,41 +4651,6 @@ void Mob::CastOnNumHitFade(uint32 spell_id) } } -/* -int Mob::SlowMitigation(bool slow_msg, Mob *caster, int slow_value) -{ - if (slow_mitigation) - return 0; - - float int_slow_mitigation = slow_mitigation * 100.0f; - - if (int_slow_mitigation > 100.0f) - return 0; - - if (slow_msg) - { - if (caster && caster->IsClient()) - { - if ((int_slow_mitigation > 0.0f) && (int_slow_mitigation < 26.0f)) - caster->Message_StringID(MT_Spells, SLOW_MOSTLY_SUCCESSFUL); - - else if ((int_slow_mitigation >= 26.0f) && (int_slow_mitigation < 74.0f)) - caster->Message_StringID(MT_Spells, SLOW_PARTIALLY_SUCCESSFUL); - - else if ((int_slow_mitigation >= 74.0f) && (int_slow_mitigation < 101.0f)) - caster->Message(262, "Your spell was slightly successful"); - } - return 0; - } - - else - { - slow_value -= (slow_value * static_cast(int_slow_mitigation) / 100); - return slow_value; - } -} -*/ - void Mob::SlowMitigation(Mob* caster) { if (GetSlowMitigation() && caster && caster->IsClient()) From 272bbdb4d217911ce8c2572c4fdfb62af9d97013 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sat, 12 Apr 2014 21:15:13 -0400 Subject: [PATCH 10/37] Revision of SE_PercentHeal Will now apply focus properly Can now do damage if set to negative value --- zone/spell_effects.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 61e220d2d..6b48627d4 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -284,20 +284,31 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Percental Heal: %+i (%d%% max)", spell.max[i], effect_value); #endif - //im not 100% sure about this implementation. - //the spell value forumula dosent work for these... at least spell 3232 anyways - int32 val = spell.max[i]; + int32 val = GetMaxHP() * spell.base[i] / 100; - if(caster) - val = caster->GetActSpellHealing(spell_id, val, this); + //This effect can also do damage by percent. + if (val < 0) { - int32 mhp = GetMaxHP(); - int32 cap = mhp * spell.base[i] / 100; + if (-val > spell.max[i]) + val = -spell.max[i]; - if(cap < val) - val = cap; + if (caster) + val = caster->GetActSpellDamage(spell_id, val, this); - if(val > 0) + } + + else + { + if (val > spell.max[i]) + val = spell.max[i]; + + if(caster) + val = caster->GetActSpellHealing(spell_id, val, this); + } + + if (val < 0) + Damage(caster, -val, spell_id, spell.skill, false, buffslot, false); + else HealDamage(val, caster); break; From 884b0291f7caca1d2dd7ac467e15b27337a220b7 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Apr 2014 12:00:33 -0500 Subject: [PATCH 11/37] Exported $client->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, msg) - Will be available for simple plugin use Exported $client->ExpeditionMessage(THIS, ExpdID, Message) - In use with custom expedition mod that will be released soon --- changelog.txt | 3 +++ zone/client.cpp | 27 ++++++++++++++++++++ zone/client.h | 1 + zone/perl_client.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+) diff --git a/changelog.txt b/changelog.txt index 083250403..eff2f9460 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/15/2014 == +Akkadius: Exported $client->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, msg) - Will be available for simple plugin use +Akkadius: Exported $client->ExpeditionMessage(THIS, ExpdID, Message) - In use with custom expedition mod that will be released soon == 04/12/2014 == Kayen: Fixed an with the slow mitigation code that would cause it to spam the message. Optimized the way the variable is handled for slow mitigation. diff --git a/zone/client.cpp b/zone/client.cpp index 827a3f3f8..0f1314211 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -8213,3 +8213,30 @@ void Client::PlayMP3(const char* fname) QueuePacket(outapp); safe_delete(outapp); } + +void Client::ExpeditionSay(const char *str, int ExpID) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (!database.RunQuery(query,MakeAnyLenString(&query, "SELECT `player_name` FROM `cust_inst_players` WHERE `inst_id` = %i", ExpID),errbuf,&result)){ + safe_delete_array(query); + return; + } + + safe_delete_array(query); + + if(result) + this->Message(14, "You say to the expedition, '%s'", str); + + while((row = mysql_fetch_row(result))) { + const char* CharName = row[0]; + if(strcmp(CharName, this->GetCleanName()) != 0) + worldserver.SendEmoteMessage(CharName, 0, 0, 14, "%s says to the expedition, '%s'", this->GetCleanName(), str); + // ChannelList->CreateChannel(ChannelName, ChannelOwner, ChannelPassword, true, atoi(row[3])); + } + + mysql_free_result(result); + +} \ No newline at end of file diff --git a/zone/client.h b/zone/client.h index 76b2c6774..87f085a44 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1170,6 +1170,7 @@ public: std::string GetAccountFlag(std::string flag); float GetDamageMultiplier(SkillUseTypes); void Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_consume); void PlayMP3(const char* fname); + void Client::ExpeditionSay(const char *str, int ExpID); int mod_client_damage(int damage, SkillUseTypes skillinuse, int hand, const ItemInst* weapon, Mob* other); bool mod_client_message(char* message, uint8 chan_num); bool mod_can_increase_skill(SkillUseTypes skillid, Mob* against_who); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index da81c03d7..0bab3cfb0 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -5898,6 +5898,64 @@ XS(XS_Client_PlayMP3) XSRETURN_EMPTY; } +XS(XS_Client_ExpeditionMessage); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_ExpeditionMessage) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Client::ExpeditionMessage(THIS, ExpdID, Message)"); + { + Client * THIS; + int ExpdID = (int)SvUV(ST(1)); + const char * Message = (const char *)SvPV_nolen(ST(2)); + dXSTARG; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + THIS->ExpeditionSay(Message, ExpdID); + } + XSRETURN_EMPTY; +} + +//Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg) + +XS(XS_Client_SendMarqueeMessage); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_SendMarqueeMessage) +{ + dXSARGS; + if (items != 7) + Perl_croak(aTHX_ "Usage: Client::SendMarqueeMessage(THIS, type, priority, fade_in, fade_out, duration, msg)"); + { + Client * THIS; + uint32 type = (uint32)SvUV(ST(1)); + uint32 priority = (uint32)SvUV(ST(2)); + uint32 fade_in = (uint32)SvUV(ST(3)); + uint32 fade_out = (uint32)SvUV(ST(4)); + uint32 duration = (uint32)SvUV(ST(5)); + std::string msg = (std::string)SvPV_nolen(ST(6)); + dXSTARG; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + THIS->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, msg); + } + XSRETURN_EMPTY; +} + #ifdef __cplusplus extern "C" #endif @@ -6134,6 +6192,8 @@ XS(boot_Client) newXSproto(strcpy(buf, "SilentMessage"), XS_Client_SilentMessage, file, "$$"); newXSproto(strcpy(buf, "PlayMP3"), XS_Client_PlayMP3, file, "$;$"); newXSproto(strcpy(buf, "SendTargetCommand"), XS_Client_SendTargetCommand, file, "$$"); + newXSproto(strcpy(buf, "ExpeditionMessage"), XS_Client_ExpeditionMessage, file, "$$$"); + newXSproto(strcpy(buf, "SendMarqueeMessage"), XS_Client_SendMarqueeMessage, file, "$$$$$$$"); XSRETURN_YES; } From fc9e7a3a3bc31b35cab5883be9364341c0917d38 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 16 Apr 2014 12:44:02 -0400 Subject: [PATCH 12/37] Fix linux compile issue with 884b0291 --- zone/client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client.h b/zone/client.h index 87f085a44..8eb31ba1e 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1170,7 +1170,7 @@ public: std::string GetAccountFlag(std::string flag); float GetDamageMultiplier(SkillUseTypes); void Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_consume); void PlayMP3(const char* fname); - void Client::ExpeditionSay(const char *str, int ExpID); + void ExpeditionSay(const char *str, int ExpID); int mod_client_damage(int damage, SkillUseTypes skillinuse, int hand, const ItemInst* weapon, Mob* other); bool mod_client_message(char* message, uint8 chan_num); bool mod_can_increase_skill(SkillUseTypes skillid, Mob* against_who); From 0b2281967b89e9b604ccc37690c75046729fc650 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Fri, 18 Apr 2014 19:37:26 -0500 Subject: [PATCH 13/37] Added #command error message suppression for those who don't want to see 'Command is not recognized' constantly - You need to have rule 'Chat:SuppressCommandErrors' set to true, this is set to false by default - Required SQL: 2014_04_18_Suppress_Command_Error.sql --- changelog.txt | 5 +++++ common/ruletypes.h | 1 + utils/sql/git/required/2014_04_18_Suppress_Command_Error.sql | 1 + zone/client.cpp | 5 +++-- 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 utils/sql/git/required/2014_04_18_Suppress_Command_Error.sql diff --git a/changelog.txt b/changelog.txt index eff2f9460..2e314b440 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,10 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/18/2014 == +Akkadius: Added #command error message suppression for those who don't want to see 'Command is not recognized' constantly + - You need to have rule 'Chat:SuppressCommandErrors' set to true, this is set to false by default + - Required SQL: 2014_04_18_Suppress_Command_Error.sql + == 04/15/2014 == Akkadius: Exported $client->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, msg) - Will be available for simple plugin use Akkadius: Exported $client->ExpeditionMessage(THIS, ExpdID, Message) - In use with custom expedition mod that will be released soon diff --git a/common/ruletypes.h b/common/ruletypes.h index fa07c752e..20bd0ce78 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -464,6 +464,7 @@ RULE_BOOL ( Chat, ServerWideAuction, true) RULE_BOOL ( Chat, EnableVoiceMacros, true) RULE_BOOL ( Chat, EnableMailKeyIPVerification, true) RULE_BOOL ( Chat, EnableAntiSpam, true) +RULE_BOOL ( Chat, SuppressCommandErrors, false) // Do not suppress by default RULE_INT ( Chat, MinStatusToBypassAntiSpam, 100) RULE_INT ( Chat, MinimumMessagesPerInterval, 4) RULE_INT ( Chat, MaximumMessagesPerInterval, 12) diff --git a/utils/sql/git/required/2014_04_18_Suppress_Command_Error.sql b/utils/sql/git/required/2014_04_18_Suppress_Command_Error.sql new file mode 100644 index 000000000..2d10ea458 --- /dev/null +++ b/utils/sql/git/required/2014_04_18_Suppress_Command_Error.sql @@ -0,0 +1 @@ +INSERT INTO `rule_values` VALUES ('0', 'Chat:SuppressCommandErrors', 'true', 'This will suppress "Command is not recognized"'); \ No newline at end of file diff --git a/zone/client.cpp b/zone/client.cpp index 0f1314211..702303c5c 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1022,11 +1022,12 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s if(command_dispatch(this, message) == -2) { if(parse->PlayerHasQuestSub(EVENT_COMMAND)) { int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); - if(i == 0) { + if(i == 0 && !RuleB(Chat, SuppressCommandErrors)) { Message(13, "Command '%s' not recognized.", message); } } else { - Message(13, "Command '%s' not recognized.", message); + if(!RuleB(Chat, SuppressCommandErrors)) + Message(13, "Command '%s' not recognized.", message); } } break; From 87994ebfba8511892a12b442228d32981937302a Mon Sep 17 00:00:00 2001 From: SecretsOTheP Date: Sun, 20 Apr 2014 00:26:54 -0400 Subject: [PATCH 14/37] smoke commits everyday --- changelog.txt | 3 +++ common/debug.cpp | 6 ------ zone/client.cpp | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/changelog.txt b/changelog.txt index 2e314b440..e1cbd7f80 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/20/2014 == +Secrets: Changed the functionality of EQDEBUG cmake flag. It now suppresses logs, but shows crashes in any scenario when set to 1. It will also now show crashes even if the log system is disabled. + == 04/18/2014 == Akkadius: Added #command error message suppression for those who don't want to see 'Command is not recognized' constantly - You need to have rule 'Chat:SuppressCommandErrors' set to true, this is set to false by default diff --git a/common/debug.cpp b/common/debug.cpp index badd337c1..632ca7b01 100644 --- a/common/debug.cpp +++ b/common/debug.cpp @@ -42,22 +42,16 @@ EQEMuLog::EQEMuLog() { for (int i=0; i= 2 pLogStatus[i] = 1 | 2; -#else - pLogStatus[i] = 0; -#endif logCallbackFmt[i] = nullptr; logCallbackBuf[i] = nullptr; logCallbackPva[i] = nullptr; } // TODO: Make this read from an ini or something, everyone has different opinions on what it should be -#if EQDEBUG < 2 pLogStatus[Status] = 2; pLogStatus[Error] = 2; pLogStatus[Quest] = 2; pLogStatus[Commands] = 1; -#endif logFileValid = true; } diff --git a/zone/client.cpp b/zone/client.cpp index 702303c5c..7aa7bd748 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -8162,7 +8162,7 @@ void Client::Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_ if(!auto_consume) //no message if the client consumed for us entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name); -#if EQDEBUG >= 1 +#if EQDEBUG >= 5 LogFile->write(EQEMuLog::Debug, "Eating from slot:%i", (int)slot); #endif } @@ -8179,7 +8179,7 @@ void Client::Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_ if(!auto_consume) //no message if the client consumed for us entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name); -#if EQDEBUG >= 1 +#if EQDEBUG >= 5 LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)slot); #endif } From f915eed0f4337ca734d40d8a091e49da358b7a10 Mon Sep 17 00:00:00 2001 From: Corysia Taware Date: Sat, 19 Apr 2014 21:45:54 -0700 Subject: [PATCH 15/37] Apparently 0 is a vaid file descriptor in OSX --- common/TCPServer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/TCPServer.cpp b/common/TCPServer.cpp index cfba6e94a..2e3abfe2f 100644 --- a/common/TCPServer.cpp +++ b/common/TCPServer.cpp @@ -97,8 +97,12 @@ void BaseTCPServer::ListenNewConnections() { from.sin_family = AF_INET; fromlen = sizeof(from); LockMutex lock(&MSock); +#ifndef DARWIN // Corysia - On OSX, 0 is a valid fd. if (!sock) return; +#else + if (sock == -1) return; +#endif // Check for pending connects #ifdef _WINDOWS From 635d28cd65a17ae995c2099a132ffd9dc69d9eaa Mon Sep 17 00:00:00 2001 From: KimLS Date: Sun, 20 Apr 2014 17:26:51 -0700 Subject: [PATCH 16/37] Change to how quest signals work --- changelog.txt | 1 + zone/questmgr.cpp | 13 +++---------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/changelog.txt b/changelog.txt index e1cbd7f80..007a4153f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,7 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- == 04/20/2014 == Secrets: Changed the functionality of EQDEBUG cmake flag. It now suppresses logs, but shows crashes in any scenario when set to 1. It will also now show crashes even if the log system is disabled. +KLS: Change to how signals work, signals with no delay will now be added to the signal queue. This addresses an odd timing issue where NPCs are in a state of life/death flux when a signal from event_death goes off. == 04/18/2014 == Akkadius: Added #command error message suppression for those who don't want to see 'Command is not recognized' constantly diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 4e5ceb1da..80fd1ef8c 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1277,16 +1277,9 @@ void QuestManager::signalwith(int npc_id, int signal_id, int wait_ms) { if(wait_ms > 0) { STimerList.push_back(SignalTimer(wait_ms, npc_id, signal_id)); return; - } - - if (npc_id<1) - { - printf("signal() bad npcid=%i\n",npc_id); - } - else - { - //initiator* signalnpc=0; - entity_list.SignalMobsByNPCID(npc_id, signal_id); + } else { + STimerList.push_back(SignalTimer(0, npc_id, signal_id)); + return; } } From c16fe3c8109fff073ff774d8897493b53ddcacb0 Mon Sep 17 00:00:00 2001 From: KimLS Date: Sun, 20 Apr 2014 17:46:43 -0700 Subject: [PATCH 17/37] CMake flags to defining what is logged and where --- CMakeLists.txt | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ changelog.txt | 3 +- common/debug.cpp | 23 +++++--------- 3 files changed, 90 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e39033656..91eff4d10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,78 @@ SET(EQEMU_DEBUG_LEVEL 5 CACHE STRING "EQEmu debug level: 10 - More errors than you ever wanted to see" ) +SET(EQEMU_LOG_LEVEL_STATUS 2 CACHE STRING "EQEmu logging level for [Status]: + 0 - Disabled + 1 - Ouput to File Enabled + 2 - Output to stdout Enabled + 3 - Output to File and stdout Enabled + 8 - Output to stderr Enabled + 9 - Output to File and stderr Enabled + 11 - Output to File, stdout and stderr Enabled" +) + +SET(EQEMU_LOG_LEVEL_NORMAL 3 CACHE STRING "EQEmu logging level for [Normal]: + 0 - Disabled + 1 - Ouput to File Enabled + 2 - Output to stdout Enabled + 3 - Output to File and stdout Enabled + 8 - Output to stderr Enabled + 9 - Output to File and stderr Enabled + 11 - Output to File, stdout and stderr Enabled" +) + +SET(EQEMU_LOG_LEVEL_ERROR 2 CACHE STRING "EQEmu logging level for [Error]: + 0 - Disabled + 1 - Ouput to File Enabled + 2 - Output to stdout Enabled + 3 - Output to File and stdout Enabled + 8 - Output to stderr Enabled + 9 - Output to File and stderr Enabled + 11 - Output to File, stdout and stderr Enabled" +) + +SET(EQEMU_LOG_LEVEL_DEBUG 3 CACHE STRING "EQEmu logging level for [Debug]: + 0 - Disabled + 1 - Ouput to File Enabled + 2 - Output to stdout Enabled + 3 - Output to File and stdout Enabled + 8 - Output to stderr Enabled + 9 - Output to File and stderr Enabled + 11 - Output to File, stdout and stderr Enabled" +) + +SET(EQEMU_LOG_LEVEL_QUEST 2 CACHE STRING "EQEmu logging level for [Quest]: + 0 - Disabled + 1 - Ouput to File Enabled + 2 - Output to stdout Enabled + 3 - Output to File and stdout Enabled + 8 - Output to stderr Enabled + 9 - Output to File and stderr Enabled + 11 - Output to File, stdout and stderr Enabled" +) + +SET(EQEMU_LOG_LEVEL_COMMANDS 1 CACHE STRING "EQEmu logging level for [Commands]: + 0 - Disabled + 1 - Ouput to File Enabled + 2 - Output to stdout Enabled + 3 - Output to File and stdout Enabled + 8 - Output to stderr Enabled + 9 - Output to File and stderr Enabled + 11 - Output to File, stdout and stderr Enabled" +) + +SET(EQEMU_LOG_LEVEL_CRASH 3 CACHE STRING "EQEmu logging level for [Crash]: + 0 - Disabled + 1 - Ouput to File Enabled + 2 - Output to stdout Enabled + 3 - Output to File and stdout Enabled + 8 - Output to stderr Enabled + 9 - Output to File and stderr Enabled + 11 - Output to File, stdout and stderr Enabled" +) + +MARK_AS_ADVANCED(EQEMU_LOG_LEVEL_STATUS EQEMU_LOG_LEVEL_NORMAL EQEMU_LOG_LEVEL_ERROR EQEMU_LOG_LEVEL_DEBUG EQEMU_LOG_LEVEL_QUEST EQEMU_LOG_LEVEL_COMMANDS EQEMU_LOG_LEVEL_CRASH) + SET(EQEMU_STREAM_SEND_RATE 1048576 CACHE STRING "Advanced: Base amount of data stream can send before throttle.") SET(EQEMU_STREAM_DECAY_RATE 78642 CACHE STRING "Advanced: Base amount of data stream recovers per tic.") SET(EQEMU_STREAM_RETRANSMIT_TIMEOUT_MUL 3.0 CACHE STRING "Advanced: Multiplier on retransmit timeout.") @@ -182,6 +254,14 @@ ADD_DEFINITIONS(-DDECAYBASE=${EQEMU_STREAM_DECAY_RATE}) ADD_DEFINITIONS(-DRETRANSMIT_TIMEOUT_MULT=${EQEMU_STREAM_RETRANSMIT_TIMEOUT_MUL}) ADD_DEFINITIONS(-DRETRANSMIT_TIMEOUT_MAX=${EQEMU_STREAM_RETRANSMIT_TIMEOUT_MAX}) ADD_DEFINITIONS(-DAVERAGE_DELTA_MAX=${EQEMU_STREAM_AVERAGE_DELTA_MAX}) +ADD_DEFINITIONS(-DLOG_LEVEL_STATUS=${EQEMU_LOG_LEVEL_STATUS}) +ADD_DEFINITIONS(-DLOG_LEVEL_NORMAL=${EQEMU_LOG_LEVEL_NORMAL}) +ADD_DEFINITIONS(-DLOG_LEVEL_ERROR=${EQEMU_LOG_LEVEL_ERROR}) +ADD_DEFINITIONS(-DLOG_LEVEL_DEBUG=${EQEMU_LOG_LEVEL_DEBUG}) +ADD_DEFINITIONS(-DLOG_LEVEL_QUEST=${EQEMU_LOG_LEVEL_QUEST}) +ADD_DEFINITIONS(-DLOG_LEVEL_COMMANDS=${EQEMU_LOG_LEVEL_COMMANDS}) +ADD_DEFINITIONS(-DLOG_LEVEL_CRASH=${EQEMU_LOG_LEVEL_CRASH}) + IF(EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS) ADD_DEFINITIONS(-DRETRANSMIT_ACKED_PACKETS=true) ELSE(EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS) diff --git a/changelog.txt b/changelog.txt index 007a4153f..ff2dc1d1c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,7 +2,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- == 04/20/2014 == Secrets: Changed the functionality of EQDEBUG cmake flag. It now suppresses logs, but shows crashes in any scenario when set to 1. It will also now show crashes even if the log system is disabled. -KLS: Change to how signals work, signals with no delay will now be added to the signal queue. This addresses an odd timing issue where NPCs are in a state of life/death flux when a signal from event_death goes off. +KLS: Change to how quest signals work, signals with no delay will now be added to the signal queue. This addresses an odd timing issue where NPCs are in a state of life/death flux when a signal from event_death goes off. +KLS: Added cmake flags to define how logging behavior works for each different log type. == 04/18/2014 == Akkadius: Added #command error message suppression for those who don't want to see 'Command is not recognized' constantly diff --git a/common/debug.cpp b/common/debug.cpp index 632ca7b01..fa114a338 100644 --- a/common/debug.cpp +++ b/common/debug.cpp @@ -35,23 +35,20 @@ static const char* FileNames[EQEMuLog::MaxLogID] = { "logs/eqemu", "logs/eqemu", static const char* LogNames[EQEMuLog::MaxLogID] = { "Status", "Normal", "Error", "Debug", "Quest", "Command", "Crash" }; EQEMuLog::EQEMuLog() { -// MOpen = new Mutex; -// MLog = new Mutex*[MaxLogID]; -// fp = new FILE*[MaxLogID]; -// pLogStatus = new uint8[MaxLogID]; for (int i=0; i Date: Sun, 20 Apr 2014 20:54:21 -0400 Subject: [PATCH 18/37] Hatelist crash fix --- changelog.txt | 1 + zone/hate_list.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/changelog.txt b/changelog.txt index ff2dc1d1c..08d664a70 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) Secrets: Changed the functionality of EQDEBUG cmake flag. It now suppresses logs, but shows crashes in any scenario when set to 1. It will also now show crashes even if the log system is disabled. KLS: Change to how quest signals work, signals with no delay will now be added to the signal queue. This addresses an odd timing issue where NPCs are in a state of life/death flux when a signal from event_death goes off. KLS: Added cmake flags to define how logging behavior works for each different log type. +Secrets: Crash fix for Hatelist crash observed == 04/18/2014 == Akkadius: Added #command error message suppression for those who don't want to see 'Command is not recognized' constantly diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index 7f986949b..2fbc528fc 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -445,6 +445,17 @@ Mob *HateList::GetMostHate(){ Mob *HateList::GetRandom() { int count = list.size(); + if(count == 0) //If we don't have any entries it'll crash getting a random 0, -1 position. + return NULL; + + if(count == 1) //No need to do all that extra work if we only have one hate entry + { + if(*list.begin()) // Just in case tHateEntry is invalidated somehow... + return (*list.begin())->ent; + + return NULL; + } + auto iterator = list.begin(); int random = MakeRandomInt(0, count - 1); for (int i = 0; i < random; i++) From fe542deb89e9ff75ed5baf6d41faa96fa0092f0a Mon Sep 17 00:00:00 2001 From: SecretsOTheP Date: Sun, 20 Apr 2014 21:00:50 -0400 Subject: [PATCH 19/37] indentation is important --- zone/hate_list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index 2fbc528fc..6980746df 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -451,7 +451,7 @@ Mob *HateList::GetRandom() if(count == 1) //No need to do all that extra work if we only have one hate entry { if(*list.begin()) // Just in case tHateEntry is invalidated somehow... - return (*list.begin())->ent; + return (*list.begin())->ent; return NULL; } From dffee38dc622254a9847e1a09d79c572d1aaf388 Mon Sep 17 00:00:00 2001 From: SecretsOTheP Date: Mon, 21 Apr 2014 22:19:12 -0400 Subject: [PATCH 20/37] It's never ogre with these hatelist crashes.. --- changelog.txt | 3 +++ zone/entity.cpp | 33 +++++++++++++++++++++++++++++++++ zone/entity.h | 1 + zone/hate_list.cpp | 20 +++++++++++++------- zone/mob.cpp | 2 -- 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/changelog.txt b/changelog.txt index 08d664a70..3a6858ffb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/21/2014 == +Secrets: Crash fix for more hatelist crashes. + == 04/20/2014 == Secrets: Changed the functionality of EQDEBUG cmake flag. It now suppresses logs, but shows crashes in any scenario when set to 1. It will also now show crashes even if the log system is disabled. KLS: Change to how quest signals work, signals with no delay will now be added to the signal queue. This addresses an odd timing issue where NPCs are in a state of life/death flux when a signal from event_death goes off. diff --git a/zone/entity.cpp b/zone/entity.cpp index ed5ee33f0..3f6130745 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1283,6 +1283,33 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets) } } +void EntityList::RemoveFromTargets(uint16 MobID, bool RemoveFromXTargets) +{ + Mob* mob = entity_list.GetMob(MobID); + + if(!mob) + return; + + auto it = mob_list.begin(); + while (it != mob_list.end()) { + Mob *m = it->second; + ++it; + + if (!m) + continue; + + m->RemoveFromHateList(mob); + + if (RemoveFromXTargets) { + if (m->IsClient()) + m->CastToClient()->RemoveXTarget(mob, false); + // FadingMemories calls this function passing the client. + else if (mob->IsClient()) + mob->CastToClient()->RemoveXTarget(m, false); + } + } +} + void EntityList::RemoveFromXTargets(Mob *mob) { auto it = client_list.begin(); @@ -2080,6 +2107,9 @@ bool EntityList::RemoveMob(uint16 delete_id) if (delete_id == 0) return true; + //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. + entity_list.RemoveFromTargets(delete_id, true); + auto it = mob_list.find(delete_id); if (it != mob_list.end()) { if (npc_list.count(delete_id)) @@ -2101,6 +2131,9 @@ bool EntityList::RemoveMob(Mob *delete_mob) if (delete_mob == 0) return true; + //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. + entity_list.RemoveFromTargets(delete_mob, true); + auto it = mob_list.begin(); while (it != mob_list.end()) { if (it->second == delete_mob) { diff --git a/zone/entity.h b/zone/entity.h index ede4b3bea..9db343921 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -290,6 +290,7 @@ public: void ExpeditionWarning(uint32 minutes_left); void RemoveFromTargets(Mob* mob, bool RemoveFromXTargets = false); + void RemoveFromTargets(uint16 mob, bool RemoveFromXTargets = false); void RemoveFromXTargets(Mob* mob); void RemoveFromAutoXTargets(Mob* mob); void ReplaceWithTarget(Mob* pOldMob, Mob*pNewTarget); diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index 6980746df..abc33866e 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -60,14 +60,16 @@ void HateList::Wipe() while(iterator != list.end()) { Mob* m = (*iterator)->ent; - parse->EventNPC(EVENT_HATE_LIST, owner->CastToNPC(), m, "0", 0); - //iterator + if(m) + { + parse->EventNPC(EVENT_HATE_LIST, owner->CastToNPC(), m, "0", 0); + if(m->IsClient()) + m->CastToClient()->DecrementAggroCount(); + } delete (*iterator); iterator = list.erase(iterator); - if(m->IsClient()) - m->CastToClient()->DecrementAggroCount(); } } @@ -203,6 +205,9 @@ void HateList::Add(Mob *ent, int32 in_hate, int32 in_dam, bool bFrenzy, bool iAd bool HateList::RemoveEnt(Mob *ent) { + if (!ent) + return NULL; + bool found = false; auto iterator = list.begin(); @@ -211,14 +216,15 @@ bool HateList::RemoveEnt(Mob *ent) if((*iterator)->ent == ent) { parse->EventNPC(EVENT_HATE_LIST, owner->CastToNPC(), ent, "0", 0); - delete (*iterator); - iterator = list.erase(iterator); found = true; if(ent->IsClient()) ent->CastToClient()->DecrementAggroCount(); - } + delete (*iterator); + iterator = list.erase(iterator); + + } else ++iterator; } diff --git a/zone/mob.cpp b/zone/mob.cpp index 48452f26a..0fa0b1c07 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -401,8 +401,6 @@ Mob::~Mob() if(!corpse || (corpse && !corpse->IsPlayerCorpse())) entity_list.QueueClients(this, &app, true); - entity_list.RemoveFromTargets(this, true); - if(trade) { Mob *with = trade->With(); if(with && with->IsClient()) { From f08799f7ff68a9d237471565ab95c7da3ae4ed10 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 22 Apr 2014 00:48:46 -0400 Subject: [PATCH 21/37] Add mez break message --- zone/StringIDs.h | 1 + zone/attack.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/zone/StringIDs.h b/zone/StringIDs.h index c916b1de0..9a222d276 100644 --- a/zone/StringIDs.h +++ b/zone/StringIDs.h @@ -304,6 +304,7 @@ #define SLOW_PARTIALLY_SUCCESSFUL 9030 // Your spell was partially successful. #define SLOW_SLIGHTLY_SUCCESSFUL 9031 //Your spell was slightly successful. #define SPELL_OPPOSITE_EFFECT 9032 //Your spell may have had the opposite effect of what you desired. +#define HAS_BEEN_AWAKENED 9037 //%1 has been awakened by %2. #define YOU_HEAL 9068 //You have healed %1 for %2 points of damage. #define YOUR_HIT_DOT 9072 //%1 has taken %2 damage from your %3. #define HIT_NON_MELEE 9073 //%1 hit %2 for %3 points of non-melee damage. diff --git a/zone/attack.cpp b/zone/attack.cpp index 324f5942d..e1f6b5135 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3616,6 +3616,8 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons //fade mez if we are mezzed if (IsMezzed()) { mlog(COMBAT__HITS, "Breaking mez due to attack."); + entity_list.MessageClose_StringID(this, true, 100, MT_WornOff, + HAS_BEEN_AWAKENED, GetCleanName(), attacker->GetCleanName()); BuffFadeByEffect(SE_Mez); } From dd1f5f6a119a2c32fad7aeed39d77d9ae9970f37 Mon Sep 17 00:00:00 2001 From: SecretsOTheP Date: Tue, 22 Apr 2014 13:24:07 -0400 Subject: [PATCH 22/37] More hatelist fixes. Trying to nail the crash :S --- zone/bot.cpp | 1 + zone/client.cpp | 3 ++- zone/entity.cpp | 6 ------ zone/hate_list.cpp | 7 +++---- zone/merc.cpp | 1 + zone/npc.cpp | 2 ++ 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 1526beddc..ae793cd47 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -223,6 +223,7 @@ Bot::~Bot() { GetPet()->Depop(); entity_list.RemoveBot(GetID()); + entity_list.RemoveFromTargets(GetID(), true); } void Bot::SetBotID(uint32 botID) { diff --git a/zone/client.cpp b/zone/client.cpp index 7aa7bd748..b2b9ff023 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -426,7 +426,8 @@ Client::~Client() { //let the stream factory know were done with this stream eqs->Close(); eqs->ReleaseFromUse(); - + //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. + entity_list.RemoveFromTargets(GetID(), true); UninitializeBuffSlots(); } diff --git a/zone/entity.cpp b/zone/entity.cpp index 3f6130745..c09348384 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -2107,9 +2107,6 @@ bool EntityList::RemoveMob(uint16 delete_id) if (delete_id == 0) return true; - //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. - entity_list.RemoveFromTargets(delete_id, true); - auto it = mob_list.find(delete_id); if (it != mob_list.end()) { if (npc_list.count(delete_id)) @@ -2131,9 +2128,6 @@ bool EntityList::RemoveMob(Mob *delete_mob) if (delete_mob == 0) return true; - //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. - entity_list.RemoveFromTargets(delete_mob, true); - auto it = mob_list.begin(); while (it != mob_list.end()) { if (it->second == delete_mob) { diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index abc33866e..d7cc9a9e5 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -205,9 +205,6 @@ void HateList::Add(Mob *ent, int32 in_hate, int32 in_dam, bool bFrenzy, bool iAd bool HateList::RemoveEnt(Mob *ent) { - if (!ent) - return NULL; - bool found = false; auto iterator = list.begin(); @@ -215,10 +212,12 @@ bool HateList::RemoveEnt(Mob *ent) { if((*iterator)->ent == ent) { + if(ent) parse->EventNPC(EVENT_HATE_LIST, owner->CastToNPC(), ent, "0", 0); found = true; - if(ent->IsClient()) + + if(ent && ent->IsClient()) ent->CastToClient()->DecrementAggroCount(); delete (*iterator); diff --git a/zone/merc.cpp b/zone/merc.cpp index c8c8da6fa..05cdbb65c 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -76,6 +76,7 @@ Merc::Merc(const NPCType* d, float x, float y, float z, float heading) Merc::~Merc() { AI_Stop(); entity_list.RemoveMerc(this->GetID()); + entity_list.RemoveFromTargets(GetID(), true); UninitializeBuffSlots(); } diff --git a/zone/npc.cpp b/zone/npc.cpp index 2611300d8..13c60965a 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -392,6 +392,8 @@ NPC::~NPC() safe_delete(reface_timer); safe_delete(swarmInfoPtr); safe_delete(qGlobals); + //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. + entity_list.RemoveFromTargets(GetID(), true); UninitializeBuffSlots(); } From 6aa0c9d694f9a3b9ee23c700792fa69df58b7ad0 Mon Sep 17 00:00:00 2001 From: SecretsOTheP Date: Tue, 22 Apr 2014 13:37:35 -0400 Subject: [PATCH 23/37] hatelist fixes to fixes to fixes --- zone/bot.cpp | 2 +- zone/client.cpp | 2 +- zone/merc.cpp | 2 +- zone/npc.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index ae793cd47..940780715 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -223,7 +223,7 @@ Bot::~Bot() { GetPet()->Depop(); entity_list.RemoveBot(GetID()); - entity_list.RemoveFromTargets(GetID(), true); + entity_list.RemoveFromTargets(this, true); } void Bot::SetBotID(uint32 botID) { diff --git a/zone/client.cpp b/zone/client.cpp index b2b9ff023..20b2b1412 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -427,7 +427,7 @@ Client::~Client() { eqs->Close(); eqs->ReleaseFromUse(); //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. - entity_list.RemoveFromTargets(GetID(), true); + entity_list.RemoveFromTargets(this, true); UninitializeBuffSlots(); } diff --git a/zone/merc.cpp b/zone/merc.cpp index 05cdbb65c..a1de41f71 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -76,7 +76,7 @@ Merc::Merc(const NPCType* d, float x, float y, float z, float heading) Merc::~Merc() { AI_Stop(); entity_list.RemoveMerc(this->GetID()); - entity_list.RemoveFromTargets(GetID(), true); + entity_list.RemoveFromTargets(this, true); UninitializeBuffSlots(); } diff --git a/zone/npc.cpp b/zone/npc.cpp index 13c60965a..0afee9b51 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -393,7 +393,7 @@ NPC::~NPC() safe_delete(swarmInfoPtr); safe_delete(qGlobals); //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. - entity_list.RemoveFromTargets(GetID(), true); + entity_list.RemoveFromTargets(this, true); UninitializeBuffSlots(); } From 564c31c54d60c8b9068e37e96bf8259e2765be32 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Wed, 23 Apr 2014 03:54:54 -0400 Subject: [PATCH 24/37] Improved SE_LimitCombatSkills will now more accurately determine if a spell is a combat proc. SE_LimitInstant will now also work when set to include instant spells. Optional SQL: utils/sql/git/optional/2014_04_23_FocusComabtProcs.sql Note: Set to false, if enabled will allow all combat procs to receive spell focuses. --- changelog.txt | 7 +++++++ common/ruletypes.h | 1 + common/spdat.cpp | 8 +++----- zone/mob.h | 1 + zone/spell_effects.cpp | 42 +++++++++++++++++++++++++++++++----------- zone/spells.cpp | 22 ++++++++++++++++++++++ 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/changelog.txt b/changelog.txt index d9022beda..a72ab499b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/23/2014 == +Kayen: Improved SE_LimitCombatSkills will now more accurately determine if a spell is a combat proc. +Kayen: SE_LimitInstant will now also work when set to include instant spells. + +Optional SQL: utils/sql/git/optional/2014_04_23_FocusComabtProcs.sql +Note: Set to false, if enabled will allow all combat procs to receive spell focuses. + == 04/21/2014 == Secrets: Crash fix for more hatelist crashes. Secrets: Hate list fixes, again. diff --git a/common/ruletypes.h b/common/ruletypes.h index 20bd0ce78..70e4ba0d2 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -309,6 +309,7 @@ RULE_INT ( Spells, FRProjectileItem_Titanium, 1113) // Item id for Titanium clie RULE_INT ( Spells, FRProjectileItem_SOF, 80684) // Item id for SOF clients for Fire 'spell projectile'. RULE_INT ( Spells, FRProjectileItem_NPC, 80684) // Item id for NPC Fire 'spell projectile'. RULE_BOOL ( Spells, UseLiveSpellProjectileGFX, false) // Use spell projectile graphics set in the spells_new table (player_1). Server must be using UF+ spell file. +RULE_BOOL ( Spells, FocusCombatProcs, false) //Allow all combat procs to receive focus effects. RULE_CATEGORY_END() RULE_CATEGORY( Combat ) diff --git a/common/spdat.cpp b/common/spdat.cpp index c75acc056..9bf01d413 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -677,11 +677,9 @@ bool IsCombatSkill(uint16 spell_id) { if (!IsValidSpell(spell_id)) return false; - - //Check if Discipline OR melee proc (from non-castable spell) - if ((spells[spell_id].mana == 0 && - (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep)) || - ((spells[spell_id].cast_time == 0) && (spells[spell_id].recast_time == 0) && (spells[spell_id].recovery_time == 0))) + + //Check if Discipline + if ((spells[spell_id].mana == 0 && (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep))) return true; return false; diff --git a/zone/mob.h b/zone/mob.h index d79f30867..0784b7925 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -500,6 +500,7 @@ public: bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); bool RemoveProcFromWeapon(uint16 spell_id, bool bAll = false); bool HasProcs() const; + bool IsCombatProc(uint16 spell_id); //Logging bool IsLoggingEnabled() const { return(logging_enabled); } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 6b48627d4..4ecb9d221 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1683,6 +1683,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) { CastToClient()->SummonHorse(spell_id); } + + break; } @@ -3997,6 +3999,15 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) bool LimitFailure = false; bool LimitInclude[MaxLimitInclude] = { false }; + /* Certain limits require only one of several Include conditions to be true. Ie. Add damage to fire OR ice spells. + 0/1 SE_LimitResist + 2/3 SE_LimitSpell + 4/5 SE_LimitEffect + 6/7 SE_LimitTarget + 8/9 SE_LimitSpellGroup: + 10/11 SE_LimitCastingSkill: + Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes + */ int FocusCount = 0; std::map >::const_iterator find_iter = aa_effects.find(aa_ID); @@ -4011,7 +4022,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) base1 = iter->second.base1; base2 = iter->second.base2; slot = iter->second.slot; - + /* AA Foci's can contain multiple focus effects within the same AA. To handle this we will not automatically return zero if a limit is found. @@ -4065,8 +4076,11 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) break; case SE_LimitInstant: - if(spell.buffduration) + if(base1 == 1 && spell.buffduration) //Fail if not instant LimitFailure = true; + if(base1 == 0 && (spell.buffduration == 0)) //Fail if instant + LimitFailure = true; + break; case SE_LimitMaxLevel: @@ -4114,13 +4128,14 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) case SE_LimitMinDur: if (base1 > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration)) LimitFailure = true; - break; + + break; case SE_LimitEffect: if(base1 < 0){ if(IsEffectInSpell(spell_id,-base1)) //Exclude LimitFailure = true; - } + } else{ LimitInclude[4] = true; if(IsEffectInSpell(spell_id,base1)) //Include @@ -4165,10 +4180,11 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) break; case SE_LimitCombatSkills: - if (base1 == 0 && IsCombatSkill(spell_id)) //Exclude Discs + if (base1 == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) //Exclude Discs / Procs LimitFailure = true; - else if (base1 == 1 && !IsCombatSkill(spell_id)) //Exclude Spells + else if (base1 == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) //Exclude Spells LimitFailure = true; + break; case SE_LimitSpellGroup: @@ -4199,7 +4215,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) //Do not use this limit more then once per spell. If multiple class, treat value like items would. if (!PassLimitClass(base1, GetClass())) LimitFailure = true; - break; + break; case SE_LimitRace: if (base1 != GetRace()) @@ -4460,8 +4476,11 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; case SE_LimitInstant: - if(spell.buffduration) + if(focus_spell.base[i] == 1 && spell.buffduration) //Fail if not instant return 0; + if(focus_spell.base[i] == 0 && (spell.buffduration == 0)) //Fail if instant + return 0; + break; case SE_LimitMaxLevel: @@ -4566,10 +4585,11 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; case SE_LimitCombatSkills: - if (focus_spell.base[i] == 0 && IsCombatSkill(spell_id)) //Exclude Disc - return 0; - else if (focus_spell.base[i] == 1 && !IsCombatSkill(spell_id)) //Include Spells + if (focus_spell.base[i] == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) //Exclude Discs / Procs return 0; + else if (focus_spell.base[i] == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) //Exclude Spells + return 0; + break; case SE_LimitSpellGroup: diff --git a/zone/spells.cpp b/zone/spells.cpp index 43a0d2b19..54e98613e 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -4966,6 +4966,28 @@ bool Mob::FindType(uint16 type, bool bOffensive, uint16 threshold) { return false; } +bool Mob::IsCombatProc(uint16 spell_id) { + + if (RuleB(Spells, FocusCombatProcs)) + return false; + + if(spell_id == SPELL_UNKNOWN) + return(false); + + if ((spells[spell_id].cast_time == 0) && (spells[spell_id].recast_time == 0) && (spells[spell_id].recovery_time == 0)) + { + + for (int i = 0; i < MAX_PROCS; i++){ + if (PermaProcs[i].spellID == spell_id || SpellProcs[i].spellID == spell_id + || SkillProcs[i].spellID == spell_id || RangedProcs[i].spellID == spell_id){ + return true; + } + } + } + + return false; +} + bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 base_spell_id) { if(spell_id == SPELL_UNKNOWN) return(false); From 9502f0aadf5b3dcbf1f85397b76d0da77133ff65 Mon Sep 17 00:00:00 2001 From: SecretsOTheP Date: Wed, 23 Apr 2014 12:50:25 -0400 Subject: [PATCH 25/37] Revert "It's never ogre with these hatelist crashes.." This reverts commit dffee38dc622254a9847e1a09d79c572d1aaf388. --- changelog.txt | 1 + zone/bot.cpp | 1 - zone/client.cpp | 3 +-- zone/entity.cpp | 27 --------------------------- zone/entity.h | 1 - zone/hate_list.cpp | 3 +++ zone/merc.cpp | 1 - zone/mob.cpp | 2 ++ zone/npc.cpp | 2 -- 9 files changed, 7 insertions(+), 34 deletions(-) diff --git a/changelog.txt b/changelog.txt index d9022beda..a96d312e6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,7 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) == 04/21/2014 == Secrets: Crash fix for more hatelist crashes. Secrets: Hate list fixes, again. +Secrets: Revert of hatelist changes. == 04/20/2014 == Secrets: Changed the functionality of EQDEBUG cmake flag. It now suppresses logs, but shows crashes in any scenario when set to 1. It will also now show crashes even if the log system is disabled. diff --git a/zone/bot.cpp b/zone/bot.cpp index 940780715..1526beddc 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -223,7 +223,6 @@ Bot::~Bot() { GetPet()->Depop(); entity_list.RemoveBot(GetID()); - entity_list.RemoveFromTargets(this, true); } void Bot::SetBotID(uint32 botID) { diff --git a/zone/client.cpp b/zone/client.cpp index 20b2b1412..7aa7bd748 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -426,8 +426,7 @@ Client::~Client() { //let the stream factory know were done with this stream eqs->Close(); eqs->ReleaseFromUse(); - //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. - entity_list.RemoveFromTargets(this, true); + UninitializeBuffSlots(); } diff --git a/zone/entity.cpp b/zone/entity.cpp index c09348384..ed5ee33f0 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1283,33 +1283,6 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets) } } -void EntityList::RemoveFromTargets(uint16 MobID, bool RemoveFromXTargets) -{ - Mob* mob = entity_list.GetMob(MobID); - - if(!mob) - return; - - auto it = mob_list.begin(); - while (it != mob_list.end()) { - Mob *m = it->second; - ++it; - - if (!m) - continue; - - m->RemoveFromHateList(mob); - - if (RemoveFromXTargets) { - if (m->IsClient()) - m->CastToClient()->RemoveXTarget(mob, false); - // FadingMemories calls this function passing the client. - else if (mob->IsClient()) - mob->CastToClient()->RemoveXTarget(m, false); - } - } -} - void EntityList::RemoveFromXTargets(Mob *mob) { auto it = client_list.begin(); diff --git a/zone/entity.h b/zone/entity.h index 9db343921..ede4b3bea 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -290,7 +290,6 @@ public: void ExpeditionWarning(uint32 minutes_left); void RemoveFromTargets(Mob* mob, bool RemoveFromXTargets = false); - void RemoveFromTargets(uint16 mob, bool RemoveFromXTargets = false); void RemoveFromXTargets(Mob* mob); void RemoveFromAutoXTargets(Mob* mob); void ReplaceWithTarget(Mob* pOldMob, Mob*pNewTarget); diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index d7cc9a9e5..d83ed4d12 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -205,6 +205,9 @@ void HateList::Add(Mob *ent, int32 in_hate, int32 in_dam, bool bFrenzy, bool iAd bool HateList::RemoveEnt(Mob *ent) { + if (!ent) + return false; + bool found = false; auto iterator = list.begin(); diff --git a/zone/merc.cpp b/zone/merc.cpp index a1de41f71..c8c8da6fa 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -76,7 +76,6 @@ Merc::Merc(const NPCType* d, float x, float y, float z, float heading) Merc::~Merc() { AI_Stop(); entity_list.RemoveMerc(this->GetID()); - entity_list.RemoveFromTargets(this, true); UninitializeBuffSlots(); } diff --git a/zone/mob.cpp b/zone/mob.cpp index 0fa0b1c07..48452f26a 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -401,6 +401,8 @@ Mob::~Mob() if(!corpse || (corpse && !corpse->IsPlayerCorpse())) entity_list.QueueClients(this, &app, true); + entity_list.RemoveFromTargets(this, true); + if(trade) { Mob *with = trade->With(); if(with && with->IsClient()) { diff --git a/zone/npc.cpp b/zone/npc.cpp index 0afee9b51..2611300d8 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -392,8 +392,6 @@ NPC::~NPC() safe_delete(reface_timer); safe_delete(swarmInfoPtr); safe_delete(qGlobals); - //Moved this from ~Mob, because it could cause a crash in some cases where a hate list was still used and that mob was the only one on the hate list. - entity_list.RemoveFromTargets(this, true); UninitializeBuffSlots(); } From d64f76227704b1062f0bed682568141606f84220 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 25 Apr 2014 08:52:49 -0400 Subject: [PATCH 26/37] Developement Branch AISpellEffects. Debug crash on NPC death. --- .../required/2014_04_25_AISpellEffects.sql | 50 ++++ zone/MobAI.cpp | 214 +++++++++++++++++- zone/attack.cpp | 3 +- zone/bonuses.cpp | 157 +++++++------ zone/mob.h | 3 +- zone/npc.cpp | 1 + zone/npc.h | 19 +- zone/zonedb.cpp | 13 ++ zone/zonedb.h | 22 ++ zone/zonedump.h | 1 + 10 files changed, 409 insertions(+), 74 deletions(-) create mode 100644 utils/sql/git/required/2014_04_25_AISpellEffects.sql diff --git a/utils/sql/git/required/2014_04_25_AISpellEffects.sql b/utils/sql/git/required/2014_04_25_AISpellEffects.sql new file mode 100644 index 000000000..4a450b312 --- /dev/null +++ b/utils/sql/git/required/2014_04_25_AISpellEffects.sql @@ -0,0 +1,50 @@ +-- TEST SQL -- + +ALTER TABLE `npc_types` ADD `npc_spells_effects` int( 11 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_spells`; + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for `npc_spells_effects_entries` +-- ---------------------------- +DROP TABLE IF EXISTS `npc_spells_effects_entries`; +CREATE TABLE `npc_spells_effects_entries` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `npc_spells_effects_id` int(11) NOT NULL DEFAULT '0', + `spell_effect_id` smallint(5) NOT NULL DEFAULT '0', + `minlevel` tinyint(3) unsigned NOT NULL, + `maxlevel` tinyint(3) unsigned NOT NULL, + `se_base` int(11) NOT NULL DEFAULT '0', + `se_limit` int(11) NOT NULL DEFAULT '0', + `se_max` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `spellsid_spellid` (`npc_spells_effects_id`,`spell_effect_id`) +) ENGINE=InnoDB AUTO_INCREMENT=18374 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Records of npc_spells_effects_entries +-- ---------------------------- +INSERT INTO `npc_spells_effects_entries` VALUES ('1', '1', '169', '0', '255', '10000', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('2', '1', '168', '0', '255', '3999', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('3', '2', '167', '0', '255', '98', '-1', '0'); + + + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for `npc_spells_effects` +-- ---------------------------- +DROP TABLE IF EXISTS `npc_spells_effects`; +CREATE TABLE `npc_spells_effects` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` tinytext, + `parent_list` int(11) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1079 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Records of npc_spells_effects +-- ---------------------------- +INSERT INTO `npc_spells_effects` VALUES ('1', 'Critical', '0'); +INSERT INTO `npc_spells_effects` VALUES ('2', 'ParentTest', '1'); diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index bcb4b131b..48814425b 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -530,6 +530,7 @@ void NPC::AI_Start(uint32 iMoveDelay) { if (NPCTypedata) { AI_AddNPCSpells(NPCTypedata->npc_spells_id); ProcessSpecialAbilities(NPCTypedata->special_abilities); + AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id); } SendTo(GetX(), GetY(), GetZ()); @@ -1879,7 +1880,7 @@ bool NPC::AI_EngagedCastCheck() { AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. mlog(AI__SPELLS, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells."); - + Shout("KAYEN"); // try casting a heal or gate if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { // try casting a heal on nearby @@ -2277,6 +2278,7 @@ create table npc_spells_entries ( */ bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID); +bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max); bool Compare_AI_Spells(AISpells_Struct i, AISpells_Struct j); bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { @@ -2351,6 +2353,110 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { return true; } +bool NPC::AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID) { + + npc_spells_effects_id = iDBSpellsEffectsID; + AIspellsEffects.clear(); + + if (iDBSpellsEffectsID == 0) + return false; + + DBnpcspellseffects_Struct* spell_effects_list = database.GetNPCSpellsEffects(iDBSpellsEffectsID); + + if (!spell_effects_list) { + return false; + } + + DBnpcspellseffects_Struct* parentlist = database.GetNPCSpellsEffects(spell_effects_list->parent_list); + + uint32 i; +#if MobAI_DEBUG_Spells >= 10 + std::cout << "Loading NPCSpellsEffects onto " << this->GetName() << ": dbspellseffectsid=" << iDBSpellsEffectsID; + if (spell_effects_list) { + std::cout << " (found, " << spell_effects_list->numentries << "), parentlist=" << spell_effects)list->parent_list; + if (spell_effects_list->parent_list) { + if (parentlist) { + std::cout << " (found, " << parentlist->numentries << ")"; + } + else + std::cout << " (not found)"; + } + } + else + std::cout << " (not found)"; + std::cout << std::endl; +#endif + + if (parentlist) { + for (i=0; inumentries; i++) { + if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spelleffectid > 0) { + if (!IsSpellEffectInList(spell_effects_list, parentlist->entries[i].spelleffectid, parentlist->entries[i].base, + parentlist->entries[i].limit, parentlist->entries[i].max)) + { + AddSpellEffectToNPCList(parentlist->entries[i].spelleffectid, + parentlist->entries[i].base, parentlist->entries[i].limit, + parentlist->entries[i].max); + } + } + } + } + + for (i=0; inumentries; i++) { + if (GetLevel() >= spell_effects_list->entries[i].minlevel && GetLevel() <= spell_effects_list->entries[i].maxlevel && spell_effects_list->entries[i].spelleffectid > 0) { + AddSpellEffectToNPCList(spell_effects_list->entries[i].spelleffectid, + spell_effects_list->entries[i].base, spell_effects_list->entries[i].limit, + spell_effects_list->entries[i].max); + } + } + + return true; +} + +void NPC::ApplyAISpellEffects(StatBonuses* newbon) +{ + if (!AI_HasSpellsEffects()) + return; + + + + for(int i=0; i < AIspellsEffects.size(); i++) + { + Shout("ApplyAISpellEffects %i %i %i %i", AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); + ApplySpellsBonuses(0, 0, newbon, 0, false, 0,-1, + true, AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); + } + + return; +} + +// adds a spell to the list, taking into account priority and resorting list as needed. +void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max) +{ + + if(!iSpellEffectID) + return; + + + HasAISpellEffects = true; + AISpellsEffects_Struct t; + + t.spelleffectid = iSpellEffectID; + t.base = base; + t.limit = limit; + t.max = max; + Shout("AddSpellEffectToNPCList %i %i %i %i", iSpellEffectID,base, limit, max ); + AIspellsEffects.push_back(t); +} + +bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max) { + for (uint32 i=0; i < spelleffect_list->numentries; i++) { + if (spelleffect_list->entries[i].spelleffectid == iSpellEffectID && spelleffect_list->entries[i].base == base + && spelleffect_list->entries[i].limit == limit && spelleffect_list->entries[i].max == max) + return true; + } + return false; +} + bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID) { for (uint32 i=0; i < spell_list->numentries; i++) { if (spell_list->entries[i].spellid == iSpellID) @@ -2415,6 +2521,7 @@ void NPC::AISpellsList(Client *c) DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { if (iDBSpellsID == 0) return 0; + if (!npc_spells_cache) { npc_spells_maxid = GetMaxNPCSpellsID(); npc_spells_cache = new DBnpcspells_Struct*[npc_spells_maxid+1]; @@ -2424,11 +2531,13 @@ DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { npc_spells_loadtried[i] = false; } } + if (iDBSpellsID > npc_spells_maxid) return 0; if (npc_spells_cache[iDBSpellsID]) { // it's in the cache, easy =) return npc_spells_cache[iDBSpellsID]; } + else if (!npc_spells_loadtried[iDBSpellsID]) { // no reason to ask the DB again if we have failed once already npc_spells_loadtried[iDBSpellsID] = true; char errbuf[MYSQL_ERRMSG_SIZE]; @@ -2439,7 +2548,7 @@ DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list, attack_proc, proc_chance from npc_spells where id=%d", iDBSpellsID), errbuf, &result)) { safe_delete_array(query); if (mysql_num_rows(result) == 1) { - row = mysql_fetch_row(result); + row = mysql_fetch_row(result); uint32 tmpparent_list = atoi(row[1]); int16 tmpattack_proc = atoi(row[2]); uint8 tmpproc_chance = atoi(row[3]); @@ -2527,3 +2636,104 @@ uint32 ZoneDatabase::GetMaxNPCSpellsID() { return 0; } +DBnpcspellseffects_Struct* ZoneDatabase::GetNPCSpellsEffects(uint32 iDBSpellsEffectsID) { + if (iDBSpellsEffectsID == 0) + return 0; + + if (!npc_spellseffects_cache) { + npc_spellseffects_maxid = GetMaxNPCSpellsEffectsID(); + npc_spellseffects_cache = new DBnpcspellseffects_Struct*[npc_spellseffects_maxid+1]; + npc_spellseffects_loadtried = new bool[npc_spellseffects_maxid+1]; + for (uint32 i=0; i<=npc_spellseffects_maxid; i++) { + npc_spellseffects_cache[i] = 0; + npc_spellseffects_loadtried[i] = false; + } + } + + if (iDBSpellsEffectsID > npc_spellseffects_maxid) + return 0; + if (npc_spellseffects_cache[iDBSpellsEffectsID]) { // it's in the cache, easy =) + return npc_spellseffects_cache[iDBSpellsEffectsID]; + } + + else if (!npc_spellseffects_loadtried[iDBSpellsEffectsID]) { // no reason to ask the DB again if we have failed once already + npc_spellseffects_loadtried[iDBSpellsEffectsID] = true; + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list from npc_spells_effects where id=%d", iDBSpellsEffectsID), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + uint32 tmpparent_list = atoi(row[1]); + mysql_free_result(result); + if (RunQuery(query, MakeAnyLenString(&query, "SELECT spell_effect_id, minlevel, maxlevel,se_base, se_limit, se_max from npc_spells_effects_entries where npc_spells_effects_id=%d ORDER BY minlevel", iDBSpellsEffectsID), errbuf, &result)) { + safe_delete_array(query); + uint32 tmpSize = sizeof(DBnpcspellseffects_Struct) + (sizeof(DBnpcspellseffects_Struct) * mysql_num_rows(result)); + npc_spellseffects_cache[iDBSpellsEffectsID] = (DBnpcspellseffects_Struct*) new uchar[tmpSize]; + memset(npc_spellseffects_cache[iDBSpellsEffectsID], 0, tmpSize); + npc_spellseffects_cache[iDBSpellsEffectsID]->parent_list = tmpparent_list; + npc_spellseffects_cache[iDBSpellsEffectsID]->numentries = mysql_num_rows(result); + int j = 0; + while ((row = mysql_fetch_row(result))) { + int spell_effect_id = atoi(row[0]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].spelleffectid = spell_effect_id; + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].minlevel = atoi(row[1]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].maxlevel = atoi(row[2]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].base = atoi(row[3]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].limit = atoi(row[4]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].max = atoi(row[5]); + j++; + } + mysql_free_result(result); + return npc_spellseffects_cache[iDBSpellsEffectsID]; + } + else { + std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + } + else { + mysql_free_result(result); + } + } + else { + std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + return 0; + } + return 0; +} + +uint32 ZoneDatabase::GetMaxNPCSpellsEffectsID() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT max(id) from npc_spells_effects"), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + uint32 ret = 0; + if (row[0]) + ret = atoi(row[0]); + mysql_free_result(result); + return ret; + } + mysql_free_result(result); + } + else { + std::cerr << "Error in GetMaxNPCSpellsEffectsID query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + + return 0; +} + diff --git a/zone/attack.cpp b/zone/attack.cpp index e1f6b5135..fbcec2f59 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2120,7 +2120,8 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack if(p_depop == true) return false; - + + HasAISpellEffects = false; BuffFadeAll(); uint8 killed_level = GetLevel(); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index c726333f2..341a62b74 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1258,6 +1258,10 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) } } + //Applies any perma NPC spell bonuses from npc_spells_effects table. + if (IsNPC()) + CastToNPC()->ApplyAISpellEffects(newbon); + //Removes the spell bonuses that are effected by a 'negate' debuff. if (spellbonuses.NegateEffects){ for(i = 0; i < buff_count; i++) { @@ -1270,12 +1274,13 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) if (GetClass() == BARD) newbon->ManaRegen = 0; // Bards do not get mana regen from spells. } -void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterId, bool item_bonus, uint32 ticsremaining, int buffslot) +void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterId, bool item_bonus, uint32 ticsremaining, int buffslot, + bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max) { - int i, effect_value; + int i, effect_value, base2, max, effectid; Mob *caster = nullptr; - if(!IsValidSpell(spell_id)) + if(!IsAISpellEffect && !IsValidSpell(spell_id)) return; if(casterId > 0) @@ -1283,19 +1288,35 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne for (i = 0; i < EFFECT_COUNT; i++) { - if(IsBlankSpellEffect(spell_id, i)) - continue; + //Buffs/Item effects + if (!IsAISpellEffect) { - uint8 focus = IsFocusEffect(spell_id, i); - if (focus) - { - newbon->FocusEffects[focus] = spells[spell_id].effectid[i]; - continue; + if(IsBlankSpellEffect(spell_id, i)) + continue; + + uint8 focus = IsFocusEffect(spell_id, i); + if (focus) + { + newbon->FocusEffects[focus] = spells[spell_id].effectid[i]; + continue; + } + + + effectid = spells[spell_id].effectid[i]; + effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining); + base2 = spells[spell_id].base2[i]; + max = spells[spell_id].max[i]; + } + //Use AISpellEffects + else { + effectid = effect_id; + effect_value = se_base; + base2 = se_limit; + max = se_max; + i = EFFECT_COUNT; //End the loop } - effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining); - - switch (spells[spell_id].effectid[i]) + switch (effectid) { case SE_CurrentHP: //regens if(effect_value > 0) { @@ -1631,27 +1652,27 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_CriticalHitChance: { - if (RuleB(Spells, AdditiveBonusValues) && item_bonus) { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->CriticalHitChance[HIGHEST_SKILL+1] += effect_value; else - newbon->CriticalHitChance[spells[spell_id].base2[i]] += effect_value; + newbon->CriticalHitChance[base2] += effect_value; } else if(effect_value < 0) { - if(spells[spell_id].base2[i] == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] > effect_value) + if(base2 == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] > effect_value) newbon->CriticalHitChance[HIGHEST_SKILL+1] = effect_value; - else if(spells[spell_id].base2[i] != -1 && newbon->CriticalHitChance[spells[spell_id].base2[i]] > effect_value) - newbon->CriticalHitChance[spells[spell_id].base2[i]] = effect_value; + else if(base2 != -1 && newbon->CriticalHitChance[base2] > effect_value) + newbon->CriticalHitChance[base2] = effect_value; } - else if(spells[spell_id].base2[i] == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] < effect_value) + else if(base2 == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] < effect_value) newbon->CriticalHitChance[HIGHEST_SKILL+1] = effect_value; - else if(spells[spell_id].base2[i] != -1 && newbon->CriticalHitChance[spells[spell_id].base2[i]] < effect_value) - newbon->CriticalHitChance[spells[spell_id].base2[i]] = effect_value; + else if(base2 != -1 && newbon->CriticalHitChance[base2] < effect_value) + newbon->CriticalHitChance[base2] = effect_value; + break; } @@ -1812,7 +1833,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_HundredHands: { if (RuleB(Spells, AdditiveBonusValues) && item_bonus) - newbon->HundredHands += spells[spell_id].base[i]; + newbon->HundredHands += effect_value; if (effect_value > 0 && effect_value > newbon->HundredHands) newbon->HundredHands = effect_value; //Increase Weapon Delay @@ -1825,7 +1846,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { if(newbon->MeleeSkillCheck < effect_value) { newbon->MeleeSkillCheck = effect_value; - newbon->MeleeSkillCheckSkill = spells[spell_id].base2[i]==-1?255:spells[spell_id].base2[i]; + newbon->MeleeSkillCheckSkill = base2==-1?255:base2; } break; } @@ -1834,13 +1855,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { if (RuleB(Spells, AdditiveBonusValues) && item_bonus){ - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->HitChanceEffect[HIGHEST_SKILL+1] += effect_value; else - newbon->HitChanceEffect[spells[spell_id].base2[i]] += effect_value; + newbon->HitChanceEffect[base2] += effect_value; } - else if(spells[spell_id].base2[i] == -1){ + else if(base2 == -1){ if ((effect_value < 0) && (newbon->HitChanceEffect[HIGHEST_SKILL+1] > effect_value)) newbon->HitChanceEffect[HIGHEST_SKILL+1] = effect_value; @@ -1852,12 +1873,12 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne else { - if ((effect_value < 0) && (newbon->HitChanceEffect[spells[spell_id].base2[i]] > effect_value)) - newbon->HitChanceEffect[spells[spell_id].base2[i]] = effect_value; + if ((effect_value < 0) && (newbon->HitChanceEffect[base2] > effect_value)) + newbon->HitChanceEffect[base2] = effect_value; - else if (!newbon->HitChanceEffect[spells[spell_id].base2[i]] || - ((newbon->HitChanceEffect[spells[spell_id].base2[i]] > 0) && (newbon->HitChanceEffect[spells[spell_id].base2[i]] < effect_value))) - newbon->HitChanceEffect[spells[spell_id].base2[i]] = effect_value; + else if (!newbon->HitChanceEffect[base2] || + ((newbon->HitChanceEffect[base2] > 0) && (newbon->HitChanceEffect[base2] < effect_value))) + newbon->HitChanceEffect[base2] = effect_value; } break; @@ -1866,19 +1887,19 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_DamageModifier: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->DamageModifier[HIGHEST_SKILL+1] += effect_value; else - newbon->DamageModifier[spells[spell_id].base2[i]] += effect_value; + newbon->DamageModifier[base2] += effect_value; break; } case SE_MinDamageModifier: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->MinDamageModifier[HIGHEST_SKILL+1] += effect_value; else - newbon->MinDamageModifier[spells[spell_id].base2[i]] += effect_value; + newbon->MinDamageModifier[base2] += effect_value; break; } @@ -1921,8 +1942,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne newbon->DeathSave[0] = effect_value; //1='Partial' 2='Full' newbon->DeathSave[1] = buffslot; //These are used in later expansion spell effects. - newbon->DeathSave[2] = spells[spell_id].base2[i];//Min level for HealAmt - newbon->DeathSave[3] = spells[spell_id].max[i];//HealAmt + newbon->DeathSave[2] = base2;//Min level for HealAmt + newbon->DeathSave[3] = max;//HealAmt } break; } @@ -1937,7 +1958,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne else if(newbon->DivineSaveChance[0] < effect_value) { newbon->DivineSaveChance[0] = effect_value; - newbon->DivineSaveChance[1] = spells[spell_id].base2[i]; + newbon->DivineSaveChance[1] = base2; //SetDeathSaveChance(true); } break; @@ -1972,10 +1993,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_SkillDamageTaken: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->SkillDmgTaken[HIGHEST_SKILL+1] += effect_value; else - newbon->SkillDmgTaken[spells[spell_id].base2[i]] += effect_value; + newbon->SkillDmgTaken[base2] += effect_value; break; } @@ -2000,8 +2021,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { newbon->CriticalSpellChance += effect_value; - if (spells[spell_id].base2[i] > newbon->SpellCritDmgIncNoStack) - newbon->SpellCritDmgIncNoStack = spells[spell_id].base2[i]; + if (base2 > newbon->SpellCritDmgIncNoStack) + newbon->SpellCritDmgIncNoStack = base2; break; } @@ -2053,9 +2074,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if(!newbon->SpellOnKill[e]) { // Base2 = Spell to fire | Base1 = % chance | Base3 = min level - newbon->SpellOnKill[e] = spells[spell_id].base2[i]; + newbon->SpellOnKill[e] = base2; newbon->SpellOnKill[e+1] = effect_value; - newbon->SpellOnKill[e+2] = spells[spell_id].max[i]; + newbon->SpellOnKill[e+2] = max; break; } } @@ -2069,7 +2090,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if(!newbon->SpellOnDeath[e]) { // Base2 = Spell to fire | Base1 = % chance - newbon->SpellOnDeath[e] = spells[spell_id].base2[i]; + newbon->SpellOnDeath[e] = base2; newbon->SpellOnDeath[e+1] = effect_value; break; } @@ -2079,26 +2100,26 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_CriticalDamageMob: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->CritDmgMob[HIGHEST_SKILL+1] += effect_value; else - newbon->CritDmgMob[spells[spell_id].base2[i]] += effect_value; + newbon->CritDmgMob[base2] += effect_value; break; } case SE_ReduceSkillTimer: { - if(newbon->SkillReuseTime[spells[spell_id].base2[i]] < effect_value) - newbon->SkillReuseTime[spells[spell_id].base2[i]] = effect_value; + if(newbon->SkillReuseTime[base2] < effect_value) + newbon->SkillReuseTime[base2] = effect_value; break; } case SE_SkillDamageAmount: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->SkillDamageAmount[HIGHEST_SKILL+1] += effect_value; else - newbon->SkillDamageAmount[spells[spell_id].base2[i]] += effect_value; + newbon->SkillDamageAmount[base2] += effect_value; break; } @@ -2188,10 +2209,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_SkillDamageAmount2: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->SkillDamageAmount2[HIGHEST_SKILL+1] += effect_value; else - newbon->SkillDamageAmount2[spells[spell_id].base2[i]] += effect_value; + newbon->SkillDamageAmount2[base2] += effect_value; break; } @@ -2219,7 +2240,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if (newbon->MeleeThresholdGuard[0] < effect_value){ newbon->MeleeThresholdGuard[0] = effect_value; newbon->MeleeThresholdGuard[1] = buffslot; - newbon->MeleeThresholdGuard[2] = spells[spell_id].base2[i]; + newbon->MeleeThresholdGuard[2] = base2; } break; } @@ -2229,7 +2250,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if (newbon->SpellThresholdGuard[0] < effect_value){ newbon->SpellThresholdGuard[0] = effect_value; newbon->SpellThresholdGuard[1] = buffslot; - newbon->SpellThresholdGuard[2] = spells[spell_id].base2[i]; + newbon->SpellThresholdGuard[2] = base2; } break; } @@ -2263,20 +2284,20 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_TriggerMeleeThreshold: { - if (newbon->TriggerMeleeThreshold[2] < spells[spell_id].base2[i]){ + if (newbon->TriggerMeleeThreshold[2] < base2){ newbon->TriggerMeleeThreshold[0] = effect_value; newbon->TriggerMeleeThreshold[1] = buffslot; - newbon->TriggerMeleeThreshold[2] = spells[spell_id].base2[i]; + newbon->TriggerMeleeThreshold[2] = base2; } break; } case SE_TriggerSpellThreshold: { - if (newbon->TriggerSpellThreshold[2] < spells[spell_id].base2[i]){ + if (newbon->TriggerSpellThreshold[2] < base2){ newbon->TriggerSpellThreshold[0] = effect_value; newbon->TriggerSpellThreshold[1] = buffslot; - newbon->TriggerSpellThreshold[2] = spells[spell_id].base2[i]; + newbon->TriggerSpellThreshold[2] = base2; } break; } @@ -2291,7 +2312,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_ShieldEquipDmgMod: newbon->ShieldEquipDmgMod[0] += effect_value; - newbon->ShieldEquipDmgMod[1] += spells[spell_id].base2[i]; + newbon->ShieldEquipDmgMod[1] += base2; break; case SE_BlockBehind: @@ -2380,7 +2401,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne break; case SE_AddSingingMod: - switch (spells[spell_id].base2[i]) + switch (base2) { case ItemTypeWindInstrument: newbon->windMod += effect_value; @@ -2474,8 +2495,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) { if(!newbon->SEResist[e] && - ((newbon->SEResist[e] = spells[spell_id].base2[i]) && (newbon->SEResist[e+1] < effect_value)) ){ - newbon->SEResist[e] = spells[spell_id].base2[i]; + ((newbon->SEResist[e] = base2) && (newbon->SEResist[e+1] < effect_value)) ){ + newbon->SEResist[e] = base2; newbon->SEResist[e+1] = effect_value; break; } @@ -2493,7 +2514,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_GiveDoubleRiposte: { //Only allow for regular double riposte chance. - if(newbon->GiveDoubleRiposte[spells[spell_id].base2[i]] == 0){ + if(newbon->GiveDoubleRiposte[base2] == 0){ if(newbon->GiveDoubleRiposte[0] < effect_value) newbon->GiveDoubleRiposte[0] = effect_value; } @@ -2504,7 +2525,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { if(newbon->SlayUndead[1] < effect_value) newbon->SlayUndead[0] = effect_value; // Rate - newbon->SlayUndead[1] = spells[spell_id].base2[i]; // Damage Modifier + newbon->SlayUndead[1] = base2; // Damage Modifier break; } @@ -2520,7 +2541,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_ImprovedTaunt: if (newbon->ImprovedTaunt[0] < effect_value) { newbon->ImprovedTaunt[0] = effect_value; - newbon->ImprovedTaunt[1] = spells[spell_id].base2[i]; + newbon->ImprovedTaunt[1] = base2; newbon->ImprovedTaunt[2] = buffslot; } break; @@ -2531,7 +2552,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne break; case SE_FrenziedDevastation: - newbon->FrenziedDevastation += spells[spell_id].base2[i]; + newbon->FrenziedDevastation += base2; break; case SE_Root: diff --git a/zone/mob.h b/zone/mob.h index 0784b7925..90013645b 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -182,7 +182,8 @@ public: bool IsBeneficialAllowed(Mob *target); virtual int GetCasterLevel(uint16 spell_id); void ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterID = 0, - bool item_bonus = false, uint32 ticsremaining = 0, int buffslot = -1); + bool item_bonus = false, uint32 ticsremaining = 0, int buffslot = -1, + bool IsAISpellEffect = false, uint16 effect_id = 0, int32 se_base = 0, int32 se_limit = 0, int32 se_max = 0); void NegateSpellsBonuses(uint16 spell_id); virtual float GetActSpellRange(uint16 spell_id, float range, bool IsBard = false) { return range;} virtual int32 GetActSpellDamage(uint16 spell_id, int32 value, Mob* target = nullptr) { return value; } diff --git a/zone/npc.cpp b/zone/npc.cpp index 2611300d8..3886d7c66 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -231,6 +231,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float npc_spells_id = 0; HasAISpell = false; + HasAISpellEffects = false; if(GetClass() == MERCERNARY_MASTER && RuleB(Mercs, AllowMercs)) { diff --git a/zone/npc.h b/zone/npc.h index 55bed4abc..ae444c099 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -66,6 +66,13 @@ struct AISpells_Struct { int16 resist_adjust; }; +struct AISpellsEffects_Struct { + uint16 spelleffectid; + int32 base; + int32 limit; + int32 max; +}; + class AA_SwarmPetInfo; class NPC : public Mob @@ -96,8 +103,11 @@ public: virtual void AI_Stop(); void AI_DoMovement(); bool AI_AddNPCSpells(uint32 iDBSpellsID); + bool AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID); virtual bool AI_EngagedCastCheck(); bool AI_HasSpells() { return HasAISpell; } + bool AI_HasSpellsEffects() { return HasAISpellEffects; } + void ApplyAISpellEffects(StatBonuses* newbon); virtual bool AI_PursueCastCheck(); virtual bool AI_IdleCastCheck(); @@ -289,6 +299,7 @@ public: inline void GiveNPCTypeData(NPCType *ours) { NPCTypedata_ours = ours; } inline const uint32 GetNPCSpellsID() const { return npc_spells_id; } + inline const uint32 GetNPCSpellsEffectsID() const { return npc_spells_effects_id; } ItemList itemlist; //kathgar - why is this public? Doing other things or I would check the code @@ -339,6 +350,7 @@ public: uint32 GetAdventureTemplate() const { return adventure_template_id; } void AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint16 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust); + void AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max); void RemoveSpellFromNPCList(int16 spell_id); Timer *GetRefaceTimer() const { return reface_timer; } const uint32 GetAltCurrencyType() const { return NPCTypedata->alt_currency_type; } @@ -400,8 +412,11 @@ protected: bool HasAISpell; virtual bool AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes); virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); - - + + uint32 npc_spells_effects_id; + std::vector AIspellsEffects; + bool HasAISpellEffects; + uint32 max_dmg; uint32 min_dmg; int32 accuracy_rating; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 893a1a27a..2178a15c9 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -33,8 +33,11 @@ ZoneDatabase::ZoneDatabase(const char* host, const char* user, const char* passw void ZoneDatabase::ZDBInitVars() { memset(door_isopen_array, 0, sizeof(door_isopen_array)); npc_spells_maxid = 0; + npc_spellseffects_maxid = 0; npc_spells_cache = 0; + npc_spellseffects_cache = 0; npc_spells_loadtried = 0; + npc_spellseffects_loadtried = 0; max_faction = 0; faction_array = nullptr; } @@ -49,6 +52,14 @@ ZoneDatabase::~ZoneDatabase() { } safe_delete_array(npc_spells_loadtried); + if (npc_spellseffects_cache) { + for (x=0; x<=npc_spellseffects_maxid; x++) { + safe_delete_array(npc_spellseffects_cache[x]); + } + safe_delete_array(npc_spellseffects_cache); + } + safe_delete_array(npc_spellseffects_loadtried); + if (faction_array != nullptr) { for (x=0; x <= max_faction; x++) { if (faction_array[x] != 0) @@ -1053,6 +1064,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { "npc_types.attack_count," "npc_types.special_abilities," "npc_types.npc_spells_id," + "npc_types.npc_spells_effects_id," "npc_types.d_meele_texture1," "npc_types.d_meele_texture2," "npc_types.prim_melee_type," @@ -1151,6 +1163,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->attack_count = atoi(row[r++]); strn0cpy(tmpNPCType->special_abilities, row[r++], 512); tmpNPCType->npc_spells_id = atoi(row[r++]); + tmpNPCType->npc_spells_effects_id = atoi(row[r++]); tmpNPCType->d_meele_texture1 = atoi(row[r++]); tmpNPCType->d_meele_texture2 = atoi(row[r++]); tmpNPCType->prim_melee_type = atoi(row[r++]); diff --git a/zone/zonedb.h b/zone/zonedb.h index 797b59980..e53b45530 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -30,6 +30,17 @@ struct DBnpcspells_entries_Struct { }; #pragma pack() +#pragma pack(1) +struct DBnpcspellseffects_entries_Struct { + int16 spelleffectid; + uint8 minlevel; + uint8 maxlevel; + int32 base; + int32 limit; + int32 max; +}; +#pragma pack() + struct DBnpcspells_Struct { uint32 parent_list; int16 attack_proc; @@ -38,6 +49,12 @@ struct DBnpcspells_Struct { DBnpcspells_entries_Struct entries[0]; }; +struct DBnpcspellseffects_Struct { + uint32 parent_list; + uint32 numentries; + DBnpcspellseffects_entries_Struct entries[0]; +}; + struct DBTradeskillRecipe_Struct { SkillUseTypes tradeskill; int16 skill_needed; @@ -345,7 +362,9 @@ public: void AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat); void AddLootDropToNPC(NPC* npc,uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop); uint32 GetMaxNPCSpellsID(); + uint32 GetMaxNPCSpellsEffectsID(); DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID); + DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID); /* * Mercs @@ -475,8 +494,11 @@ protected: uint32 max_faction; Faction** faction_array; uint32 npc_spells_maxid; + uint32 npc_spellseffects_maxid; DBnpcspells_Struct** npc_spells_cache; bool* npc_spells_loadtried; + DBnpcspellseffects_Struct** npc_spellseffects_cache; + bool* npc_spellseffects_loadtried; uint8 door_isopen_array[255]; }; diff --git a/zone/zonedump.h b/zone/zonedump.h index 39c6b39d7..e18734d4f 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -53,6 +53,7 @@ struct NPCType uint8 helmtexture; uint32 loottable_id; uint32 npc_spells_id; + uint32 npc_spells_effects_id; int32 npc_faction_id; uint32 merchanttype; uint32 alt_currency_type; From 391b6ed515c7731e2d5ca9c6d3008a2e701d89fc Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 25 Apr 2014 09:07:48 -0400 Subject: [PATCH 27/37] fix --- utils/sql/git/required/2014_04_25_AISpellEffects.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/sql/git/required/2014_04_25_AISpellEffects.sql b/utils/sql/git/required/2014_04_25_AISpellEffects.sql index 4a450b312..a85b117bc 100644 --- a/utils/sql/git/required/2014_04_25_AISpellEffects.sql +++ b/utils/sql/git/required/2014_04_25_AISpellEffects.sql @@ -1,6 +1,6 @@ -- TEST SQL -- -ALTER TABLE `npc_types` ADD `npc_spells_effects` int( 11 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_spells`; +ALTER TABLE `npc_types` ADD `npc_spells_effects_id` int( 11 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_spells_id`; SET FOREIGN_KEY_CHECKS=0; From d8ad337c0ef39b57624e8dacc31400c0903f8a8f Mon Sep 17 00:00:00 2001 From: cavedude00 Date: Fri, 25 Apr 2014 12:40:25 -0700 Subject: [PATCH 28/37] Fixed a zone crash in spawn_conditions. spawn_events changes, fixes, and additions. --- changelog.txt | 11 ++ .../git/required/2014_04_25_spawn_events.sql | 1 + zone/MobAI.cpp | 21 +++ zone/embparser_api.cpp | 9 +- zone/lua_general.cpp | 4 +- zone/questmgr.cpp | 4 +- zone/questmgr.h | 2 +- zone/spawn2.cpp | 158 +++++++++++------- zone/spawn2.h | 4 +- 9 files changed, 140 insertions(+), 74 deletions(-) create mode 100644 utils/sql/git/required/2014_04_25_spawn_events.sql diff --git a/changelog.txt b/changelog.txt index 04221cf97..fe35332b6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,16 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/25/2014 == +cavedude: Corrected a crash in spawn_conditions caused by NPCs on a one way path. +cavedude: Added strict column to spawn_events which will prevent an event from enabling if it's mid-cycle. +cavedude: Prevented disabled or strict spawn_events from enabling when the zone first boots. +cavedude: Fixed the quest function toggle_spawn_event under Perl. + +If you're using the quest function toggle_spawn_event (worked on Lua only) it has changed syntax to: +toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base) + +Required SQL: utils/sql/git/required/2014_04_25_spawn_events.sql + == 04/23/2014 == Kayen: Improved SE_LimitCombatSkills will now more accurately determine if a spell is a combat proc. Kayen: SE_LimitInstant will now also work when set to include instant spells. diff --git a/utils/sql/git/required/2014_04_25_spawn_events.sql b/utils/sql/git/required/2014_04_25_spawn_events.sql new file mode 100644 index 000000000..6a96444e9 --- /dev/null +++ b/utils/sql/git/required/2014_04_25_spawn_events.sql @@ -0,0 +1 @@ +alter table spawn_events add column `strict` tinyint(4) not null default 0; \ No newline at end of file diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index bcb4b131b..681d6031b 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -1627,11 +1627,32 @@ void NPC::AI_DoMovement() { if (gridno > 0 || cur_wp==-2) { if (movetimercompleted==true) { // time to pause at wp is over + + int32 spawn_id = this->GetSpawnPointID(); + LinkedListIterator iterator(zone->spawn2_list); + iterator.Reset(); + Spawn2 *found_spawn = nullptr; + + while(iterator.MoreElements()) + { + Spawn2* cur = iterator.GetData(); + iterator.Advance(); + if(cur->GetID() == spawn_id) + { + found_spawn = cur; + break; + } + } + if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) { CastToNPC()->Depop(true); //depop and resart spawn timer + if(found_spawn) + found_spawn->SetNPCPointerNull(); } else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) { CastToNPC()->Depop(false);//depop without spawn timer + if(found_spawn) + found_spawn->SetNPCPointerNull(); } else { movetimercompleted=false; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 6a7114786..91b6d1836 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -1678,14 +1678,15 @@ XS(XS__toggle_spawn_event); XS(XS__toggle_spawn_event) { dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: toggle_spawn_event(event_id, enabled?, reset_base)"); + if (items != 4) + Perl_croak(aTHX_ "Usage: toggle_spawn_event(event_id, enabled?, strict, reset_base)"); uint32 event_id = (int)SvIV(ST(0)); bool enabled = ((int)SvIV(ST(1))) == 0?false:true; - bool reset_base = ((int)SvIV(ST(1))) == 0?false:true; + bool strict = ((int)SvIV(ST(2))) == 0?false:true; + bool reset_base = ((int)SvIV(ST(3))) == 0?false:true; - quest_manager.toggle_spawn_event(event_id, enabled, reset_base); + quest_manager.toggle_spawn_event(event_id, enabled, strict, reset_base); XSRETURN_EMPTY; } diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index c6e551d85..bd788695a 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -372,8 +372,8 @@ int lua_get_spawn_condition(const char *zone, uint32 instance_id, int condition_ return quest_manager.get_spawn_condition(zone, instance_id, condition_id); } -void lua_toggle_spawn_event(int event_id, bool enable, bool reset) { - quest_manager.toggle_spawn_event(event_id, enable, reset); +void lua_toggle_spawn_event(int event_id, bool enable, bool strict, bool reset) { + quest_manager.toggle_spawn_event(event_id, enable, strict, reset); } void lua_summon_burried_player_corpse(uint32 char_id, float x, float y, float z, float h) { diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 80fd1ef8c..ab5e79087 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1733,8 +1733,8 @@ short QuestManager::get_spawn_condition(const char *zone_short, uint32 instance_ } //toggle a spawn event -void QuestManager::toggle_spawn_event(int event_id, bool enable, bool reset_base) { - zone->spawn_conditions.ToggleEvent(event_id, enable, reset_base); +void QuestManager::toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base) { + zone->spawn_conditions.ToggleEvent(event_id, enable, strict, reset_base); } bool QuestManager::has_zone_flag(int zone_id) { diff --git a/zone/questmgr.h b/zone/questmgr.h index 81ccc58c2..89a0eca78 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -150,7 +150,7 @@ public: void showgrid(int gridid); void spawn_condition(const char *zone_short, uint32 instance_id, uint16 condition_id, short new_value); short get_spawn_condition(const char *zone_short, uint32 instance_id, uint16 condition_id); - void toggle_spawn_event(int event_id, bool enable, bool reset_base); + void toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base); bool has_zone_flag(int zone_id); void set_zone_flag(int zone_id); void clear_zone_flag(int zone_id); diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index 9b4963fd4..8d2d47fc0 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -303,11 +303,13 @@ void Spawn2::ForceDespawn() { npcthis->Depop(true); IsDespawned = true; + npcthis = nullptr; return; } else { npcthis->Depop(false); + npcthis = nullptr; } } } @@ -555,6 +557,7 @@ SpawnEvent::SpawnEvent() { action = ActionSet; argument = 0; period = 0xFFFFFFFF; + strict = false; memset(&next, 0, sizeof(next)); } @@ -586,26 +589,28 @@ void SpawnConditionManager::Process() { for(; cur != end; ++cur) { SpawnEvent &cevent = *cur; - if(!cevent.enabled) - continue; + if(cevent.enabled) + { + if(EQTime::IsTimeBefore(&tod, &cevent.next)) { + //this event has been triggered. + //execute the event + if(!cevent.strict || (cevent.strict && cevent.next.hour == tod.hour && cevent.next.day == tod.day && cevent.next.month == tod.month && cevent.next.year == tod.year)) + ExecEvent(cevent, true); - if(EQTime::IsTimeBefore(&tod, &cevent.next)) { - //this event has been triggered. - //execute the event - ExecEvent(cevent, true); - //add the period of the event to the trigger time - EQTime::AddMinutes(cevent.period, &cevent.next); - std::string t; - EQTime::ToString(&cevent.next, t); - _log(SPAWNS__CONDITIONS, "Event %d: Will trigger again in %d EQ minutes at %s.", cevent.id, cevent.period, t.c_str()); - //save the next event time in the DB - UpdateDBEvent(cevent); - //find the next closest event timer. - FindNearestEvent(); - //minor optimization, if theres no more possible events, - //then stop trying... I dunno how worth while this is. - if(EQTime::IsTimeBefore(&next_event, &tod)) - return; + //add the period of the event to the trigger time + EQTime::AddMinutes(cevent.period, &cevent.next); + std::string t; + EQTime::ToString(&cevent.next, t); + _log(SPAWNS__CONDITIONS, "Event %d: Will trigger again in %d EQ minutes at %s.", cevent.id, cevent.period, t.c_str()); + //save the next event time in the DB + UpdateDBEvent(cevent); + //find the next closest event timer. + FindNearestEvent(); + //minor optimization, if theres no more possible events, + //then stop trying... I dunno how worth while this is. + if(EQTime::IsTimeBefore(&next_event, &tod)) + return; + } } } } @@ -619,6 +624,14 @@ void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) { return; //unable to find the spawn condition to operate on } + TimeOfDay_Struct tod; + zone->zone_time.getEQTimeOfDay(&tod); + if(event.strict && (event.next.hour != tod.hour || event.next.day != tod.day || event.next.month != tod.month || event.next.year != tod.year)) + { + _log(SPAWNS__CONDITIONS, "Event %d: Unable to execute. Condition is strict, and event time has already passed.", event.id); + return; + } + SpawnCondition &cond = condi->second; int16 new_value = cond.value; @@ -666,10 +679,10 @@ void SpawnConditionManager::UpdateDBEvent(SpawnEvent &event) { len = MakeAnyLenString(&query, "UPDATE spawn_events SET " "next_minute=%d, next_hour=%d, next_day=%d, next_month=%d, " - "next_year=%d, enabled=%d " + "next_year=%d, enabled=%d, strict=%d " "WHERE id=%d", event.next.minute, event.next.hour, event.next.day, event.next.month, - event.next.year, event.enabled?1:0, event.id + event.next.year, event.enabled?1:0, event.strict?1:0,event.id ); if(!database.RunQuery(query, len, errbuf)) { LogFile->write(EQEMuLog::Error, "Unable to update spawn event '%s': %s\n", query, errbuf); @@ -703,7 +716,7 @@ bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std: bool ret = false; len = MakeAnyLenString(&query, - "SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,zone " + "SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,strict,zone " "FROM spawn_events WHERE id=%d", event_id); if (database.RunQuery(query, len, errbuf, &result)) { safe_delete_array(query); @@ -721,12 +734,13 @@ bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std: event.enabled = atoi(row[8])==0?false:true; event.action = (SpawnEvent::Action) atoi(row[9]); event.argument = atoi(row[10]); - zone_name = row[11]; + event.strict = atoi(row[11])==0?false:true; + zone_name = row[12]; std::string t; EQTime::ToString(&event.next, t); - _log(SPAWNS__CONDITIONS, "Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d. Will trigger at %s", - event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, t.c_str()); + _log(SPAWNS__CONDITIONS, "(LoadDBEvent) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d. Will trigger at %s", + event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict, t.c_str()); ret = true; } @@ -794,7 +808,7 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in //load spawn events SpawnEvent event; len = MakeAnyLenString(&query, - "SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument " + "SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,strict " "FROM spawn_events WHERE zone='%s'", zone_name); if (database.RunQuery(query, len, errbuf, &result)) { safe_delete_array(query); @@ -816,10 +830,11 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in event.enabled = atoi(row[8])==0?false:true; event.action = (SpawnEvent::Action) atoi(row[9]); event.argument = atoi(row[10]); + event.strict = atoi(row[11])==0?false:true; spawn_events.push_back(event); - _log(SPAWNS__CONDITIONS, "Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d", - event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument); + _log(SPAWNS__CONDITIONS, "(LoadSpawnConditions) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d", + event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict); } mysql_free_result(result); } else { @@ -847,34 +862,48 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in for(; cur != end; ++cur) { SpawnEvent &cevent = *cur; - if(!cevent.enabled) - continue; + bool StrictCheck = false; + if(cevent.strict && + cevent.next.hour == tod.hour && + cevent.next.day == tod.day && + cevent.next.month == tod.month && + cevent.next.year == tod.year) + StrictCheck = true; - //watch for special case of all 0s, which means to reset next to now - if(cevent.next.year == 0 && cevent.next.month == 0 && cevent.next.day == 0 && cevent.next.hour == 0 && cevent.next.minute == 0) { - _log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", cevent.id); - memcpy(&cevent.next, &tod, sizeof(cevent.next)); - //add one period - EQTime::AddMinutes(cevent.period, &cevent.next); - //save it in the db. - UpdateDBEvent(cevent); - continue; //were done with this event. - } + //If event is disabled, or we failed the strict check, set initial spawn_condition to 0. + if(!cevent.enabled || !StrictCheck) + SetCondition(zone->GetShortName(), zone->GetInstanceID(),cevent.condition_id,0); - ran = false; - while(EQTime::IsTimeBefore(&tod, &cevent.next)) { - _log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id); - //this event has been triggered. - //execute the event - ExecEvent(cevent, false); - //add the period of the event to the trigger time - EQTime::AddMinutes(cevent.period, &cevent.next); - ran = true; - } - //only write it out if the event actually ran - if(ran) { - //save the event in the DB - UpdateDBEvent(cevent); + if(cevent.enabled) + { + //watch for special case of all 0s, which means to reset next to now + if(cevent.next.year == 0 && cevent.next.month == 0 && cevent.next.day == 0 && cevent.next.hour == 0 && cevent.next.minute == 0) { + _log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", cevent.id); + memcpy(&cevent.next, &tod, sizeof(cevent.next)); + //add one period + EQTime::AddMinutes(cevent.period, &cevent.next); + //save it in the db. + UpdateDBEvent(cevent); + continue; //were done with this event. + } + + ran = false; + while(EQTime::IsTimeBefore(&tod, &cevent.next)) { + _log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id); + //this event has been triggered. + //execute the event + if(!cevent.strict || StrictCheck) + ExecEvent(cevent, false); + + //add the period of the event to the trigger time + EQTime::AddMinutes(cevent.period, &cevent.next); + ran = true; + } + //only write it out if the event actually ran + if(ran) { + //save the event in the DB + UpdateDBEvent(cevent); + } } } @@ -894,14 +923,14 @@ void SpawnConditionManager::FindNearestEvent() { int next_id = -1; for(; cur != end; ++cur) { SpawnEvent &cevent = *cur; - - if(!cevent.enabled) - continue; - - //see if this event is before our last nearest - if(EQTime::IsTimeBefore(&next_event, &cevent.next)) { - memcpy(&next_event, &cevent.next, sizeof(next_event)); - next_id = cevent.id; + if(cevent.enabled) + { + //see if this event is before our last nearest + if(EQTime::IsTimeBefore(&next_event, &cevent.next)) + { + memcpy(&next_event, &cevent.next, sizeof(next_event)); + next_id = cevent.id; + } } } if(next_id == -1) @@ -1035,7 +1064,7 @@ void SpawnConditionManager::ReloadEvent(uint32 event_id) { FindNearestEvent(); } -void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool reset_base) { +void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool strict, bool reset_base) { _log(SPAWNS__CONDITIONS, "Request to %s spawn event %d %sresetting trigger time", enabled?"enable":"disable", event_id, reset_base?"":"without "); @@ -1048,8 +1077,9 @@ void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool rese if(cevent.id == event_id) { //make sure were actually changing something - if(cevent.enabled != enabled || reset_base) { + if(cevent.enabled != enabled || reset_base || cevent.strict != strict) { cevent.enabled = enabled; + cevent.strict = strict; if(reset_base) { _log(SPAWNS__CONDITIONS, "Spawn event %d located in this zone. State set. Trigger time reset (period %d).", event_id, cevent.period); //start with the time now diff --git a/zone/spawn2.h b/zone/spawn2.h index 42c2153f2..e529bb65e 100644 --- a/zone/spawn2.h +++ b/zone/spawn2.h @@ -67,6 +67,7 @@ public: bool NPCPointerValid() { return (npcthis!=nullptr); } void SetNPCPointer(NPC* n) { npcthis = n; } + void SetNPCPointerNull() { npcthis = nullptr; } void SetTimer(uint32 duration) { timer.Start(duration); } uint32 GetKillCount() { return killcount; } protected: @@ -134,6 +135,7 @@ public: bool enabled; Action action; int16 argument; + bool strict; uint32 period; //eq minutes (3 seconds) between events TimeOfDay_Struct next; //next time this event triggers @@ -148,7 +150,7 @@ public: int16 GetCondition(const char *zone_short, uint32 instance_id, uint16 condition_id); void SetCondition(const char *zone_short, uint32 instance_id, uint16 condition_id, int16 new_value, bool world_update = false); - void ToggleEvent(uint32 event_id, bool enabled, bool reset_base); + void ToggleEvent(uint32 event_id, bool enabled, bool strict, bool reset_base); bool Check(uint16 condition, int16 min_value); void ReloadEvent(uint32 event_id); From 5e81848445d36d62f21d2d24c9a1edcf2836a09f Mon Sep 17 00:00:00 2001 From: KimLS Date: Fri, 25 Apr 2014 18:45:19 -0700 Subject: [PATCH 29/37] Darwin caused ipc mutex to stop working for non-darwin, non-windows systems. Hopefully fixed --- common/ipc_mutex.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/common/ipc_mutex.cpp b/common/ipc_mutex.cpp index 331144eca..c05fb072c 100644 --- a/common/ipc_mutex.cpp +++ b/common/ipc_mutex.cpp @@ -55,17 +55,22 @@ namespace EQEmu { std::string final_name = name; final_name += ".lock"; -#ifdef __DARWIN +#ifdef __DARWIN #if __DARWIN_C_LEVEL < 200809L imp_->fd_ = open(final_name.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); #else - imp_->fd_ = open(final_name.c_str(), - O_RDWR | O_CREAT | O_CLOEXEC, - S_IRUSR | S_IWUSR); + imp_->fd_ = open(final_name.c_str(), + O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR); #endif +#else + imp_->fd_ = open(final_name.c_str(), + O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR); #endif + if(imp_->fd_ == -1) { EQ_EXCEPT("IPC Mutex", "Could not create mutex."); } From 380cf8691a0fb0c9fd27c2544fad5500ad778db4 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sun, 27 Apr 2014 03:57:14 -0400 Subject: [PATCH 30/37] Implemented new table 'npc_spells_effects' and 'npc_spells_effects_entires'. Implemented new field in 'npc_spell_effects_id' in npc_types. These are used to directly apply spell effect bonuses to NPC's without requirings spells/buffs. Example: Allow an npc to spawn with an innate 50 pt damage shield and a 5% chance to critical hit. Please see the wiki page: http://wiki.eqemulator.org/p?npc_spell_effects_entries for details. *NPC's can now do critical heals / damage spells if bonus is applied from table. Required SQL: utils/sql/git/required/2014_04_27_AISpellEffects.sql Note: 30 examples of spell effects have been included by default in this sql. Edited/removed as needed. --- changelog.txt | 14 +++ .../required/2014_04_27_AISpellEffects.sql | 104 ++++++++++++++++++ zone/MobAI.cpp | 9 +- zone/bonuses.cpp | 42 +++++-- zone/effects.cpp | 44 +++++++- zone/mob.cpp | 28 ++--- zone/spell_effects.cpp | 2 +- 7 files changed, 208 insertions(+), 35 deletions(-) create mode 100644 utils/sql/git/required/2014_04_27_AISpellEffects.sql diff --git a/changelog.txt b/changelog.txt index fe35332b6..92bb01e61 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,19 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/27/2014 == +Kayen: Implemented new table 'npc_spells_effects' and 'npc_spells_effects_entires'. + Implemented new field in 'npc_spell_effects_id' in npc_types. + + +These are used to directly apply spell effect bonuses to NPC's without requirings spells/buffs. +Example: Allow an npc to spawn with an innate 50 pt damage shield and a 5% chance to critical hit. +Please see the wiki page: http://wiki.eqemulator.org/p?npc_spell_effects_entries for details. +*NPC's can now do critical heals / damage spells if bonus is applied from table. + +Required SQL: utils/sql/git/required/2014_04_27_AISpellEffects.sql +Note: 30 examples of spell effects have been included by default in this sql. Edited/removed as needed. + + == 04/25/2014 == cavedude: Corrected a crash in spawn_conditions caused by NPCs on a one way path. cavedude: Added strict column to spawn_events which will prevent an event from enabling if it's mid-cycle. diff --git a/utils/sql/git/required/2014_04_27_AISpellEffects.sql b/utils/sql/git/required/2014_04_27_AISpellEffects.sql new file mode 100644 index 000000000..6a5645513 --- /dev/null +++ b/utils/sql/git/required/2014_04_27_AISpellEffects.sql @@ -0,0 +1,104 @@ +-- Note: The data entered into the new table are only examples and can be deleted/modified as needed. + +ALTER TABLE `npc_types` ADD `npc_spells_effects_id` int( 11 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_spells_id`; + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for `npc_spells_effects` +-- ---------------------------- +DROP TABLE IF EXISTS `npc_spells_effects`; +CREATE TABLE `npc_spells_effects` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` tinytext, + `parent_list` int(11) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1080 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Records of npc_spells_effects +-- ---------------------------- +INSERT INTO `npc_spells_effects` VALUES ('1', 'Critical Melee [All Skills]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('2', 'Damage Shield', '0'); +INSERT INTO `npc_spells_effects` VALUES ('3', 'Melee Haste', '0'); +INSERT INTO `npc_spells_effects` VALUES ('4', 'Resist Spell Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('5', 'Resist Direct Dmg Spell Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('6', 'Reflect Spell Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('7', 'Spell Damage Shield', '0'); +INSERT INTO `npc_spells_effects` VALUES ('8', 'Melee Mitigation [All]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('9', 'Avoid Melee', '0'); +INSERT INTO `npc_spells_effects` VALUES ('10', 'Riposte Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('11', 'Dodge Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('12', 'Parry Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('13', 'Decrease Dmg Taken [2HS]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('14', 'Increase Dmg Taken [1HS]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('15', 'Block Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('16', 'Melee Lifetap', '0'); +INSERT INTO `npc_spells_effects` VALUES ('17', 'Hit Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('18', 'Increase Dmg [1HS]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('19', 'Increase Archery Dmg', '0'); +INSERT INTO `npc_spells_effects` VALUES ('20', 'Flurry Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('21', 'Add Damage [2HS]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('22', 'Divine Aura', '0'); +INSERT INTO `npc_spells_effects` VALUES ('23', 'Cast CH on Kill', '0'); +INSERT INTO `npc_spells_effects` VALUES ('24', 'Critical Heal', '0'); +INSERT INTO `npc_spells_effects` VALUES ('25', 'Critical Direct Dmg', '0'); +INSERT INTO `npc_spells_effects` VALUES ('26', 'Heal Rate', '0'); +INSERT INTO `npc_spells_effects` VALUES ('27', 'Negate Damage Shield', '0'); +INSERT INTO `npc_spells_effects` VALUES ('28', 'Increase Spell Vulnerability [All]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('29', 'Decrease Spell Vulnerability [FR]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('30', 'Movement Speed', '0'); + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for `npc_spells_effects_entries` +-- ---------------------------- +DROP TABLE IF EXISTS `npc_spells_effects_entries`; +CREATE TABLE `npc_spells_effects_entries` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `npc_spells_effects_id` int(11) NOT NULL DEFAULT '0', + `spell_effect_id` smallint(5) NOT NULL DEFAULT '0', + `minlevel` tinyint(3) unsigned NOT NULL, + `maxlevel` tinyint(3) unsigned NOT NULL, + `se_base` int(11) NOT NULL DEFAULT '0', + `se_limit` int(11) NOT NULL DEFAULT '0', + `se_max` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `spellsid_spellid` (`npc_spells_effects_id`,`spell_effect_id`) +) ENGINE=InnoDB AUTO_INCREMENT=18374 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Records of npc_spells_effects_entries +-- ---------------------------- +INSERT INTO `npc_spells_effects_entries` VALUES ('1', '1', '169', '0', '255', '10000', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('2', '2', '59', '0', '255', '-60', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('3', '3', '11', '0', '255', '150', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('4', '4', '180', '0', '255', '50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('5', '5', '378', '0', '255', '85', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('6', '6', '158', '0', '255', '50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('7', '7', '157', '0', '255', '-300', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('8', '8', '168', '0', '255', '-50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('9', '9', '172', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('10', '10', '173', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('11', '11', '174', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('12', '12', '175', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('13', '13', '197', '0', '255', '-80', '3', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('14', '14', '197', '0', '255', '80', '1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('15', '15', '188', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('16', '16', '178', '0', '255', '90', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('17', '17', '184', '0', '255', '10000', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('18', '18', '185', '0', '255', '100', '1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('19', '19', '301', '0', '255', '100', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('20', '20', '279', '0', '255', '50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('21', '21', '220', '0', '255', '2000', '1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('22', '22', '40', '0', '255', '1', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('23', '23', '360', '0', '255', '100', '13', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('24', '24', '274', '0', '255', '90', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('25', '25', '294', '0', '255', '100', '200', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('26', '26', '120', '0', '255', '50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('27', '27', '382', '0', '255', '0', '55', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('28', '28', '296', '0', '255', '1000', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('29', '29', '296', '0', '255', '-50', '2', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('30', '30', '3', '0', '255', '60', '0', '0'); + diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index 7574f19ad..3128eb111 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -1901,7 +1901,7 @@ bool NPC::AI_EngagedCastCheck() { AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. mlog(AI__SPELLS, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells."); - Shout("KAYEN"); + // try casting a heal or gate if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { // try casting a heal on nearby @@ -2437,12 +2437,9 @@ void NPC::ApplyAISpellEffects(StatBonuses* newbon) { if (!AI_HasSpellsEffects()) return; - - for(int i=0; i < AIspellsEffects.size(); i++) { - Shout("ApplyAISpellEffects %i %i %i %i", AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); ApplySpellsBonuses(0, 0, newbon, 0, false, 0,-1, true, AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); } @@ -2456,7 +2453,6 @@ void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit if(!iSpellEffectID) return; - HasAISpellEffects = true; AISpellsEffects_Struct t; @@ -2465,7 +2461,6 @@ void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit t.base = base; t.limit = limit; t.max = max; - Shout("AddSpellEffectToNPCList %i %i %i %i", iSpellEffectID,base, limit, max ); AIspellsEffects.push_back(t); } @@ -2692,7 +2687,7 @@ DBnpcspellseffects_Struct* ZoneDatabase::GetNPCSpellsEffects(uint32 iDBSpellsEff mysql_free_result(result); if (RunQuery(query, MakeAnyLenString(&query, "SELECT spell_effect_id, minlevel, maxlevel,se_base, se_limit, se_max from npc_spells_effects_entries where npc_spells_effects_id=%d ORDER BY minlevel", iDBSpellsEffectsID), errbuf, &result)) { safe_delete_array(query); - uint32 tmpSize = sizeof(DBnpcspellseffects_Struct) + (sizeof(DBnpcspellseffects_Struct) * mysql_num_rows(result)); + uint32 tmpSize = sizeof(DBnpcspellseffects_Struct) + (sizeof(DBnpcspellseffects_entries_Struct) * mysql_num_rows(result)); npc_spellseffects_cache[iDBSpellsEffectsID] = (DBnpcspellseffects_Struct*) new uchar[tmpSize]; memset(npc_spellseffects_cache[iDBSpellsEffectsID], 0, tmpSize); npc_spellseffects_cache[iDBSpellsEffectsID]->parent_list = tmpparent_list; diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 341a62b74..db0f81115 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1158,14 +1158,20 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) break; } + case SE_SpellEffectResistChance: { for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) { - if(!newbon->SEResist[e] || ((newbon->SEResist[e] = base2) && (newbon->SEResist[e+1] < base1)) ){ - newbon->SEResist[e] = base2; - newbon->SEResist[e+1] = base1; - break; + if(newbon->SEResist[e+1] && (newbon->SEResist[e] == base2) && (newbon->SEResist[e+1] < base1)){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = base1; //Resist Chance + break; + } + else if (!newbon->SEResist[e+1]){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = base1; //Resist Chance + break; } } break; @@ -1791,10 +1797,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne newbon->MeleeLifetap += spells[spell_id].base[i]; else if((effect_value < 0) && (newbon->MeleeLifetap > effect_value)) - newbon->MeleeLifetap = spells[spell_id].base[i]; + newbon->MeleeLifetap = effect_value; - else if(newbon->MeleeLifetap < spells[spell_id].base[i]) - newbon->MeleeLifetap = spells[spell_id].base[i]; + else if(newbon->MeleeLifetap < effect_value) + newbon->MeleeLifetap = effect_value; break; } @@ -2494,10 +2500,14 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) { - if(!newbon->SEResist[e] && - ((newbon->SEResist[e] = base2) && (newbon->SEResist[e+1] < effect_value)) ){ - newbon->SEResist[e] = base2; - newbon->SEResist[e+1] = effect_value; + if(newbon->SEResist[e+1] && (newbon->SEResist[e] == base2) && (newbon->SEResist[e+1] < effect_value)){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = effect_value; //Resist Chance + break; + } + else if (!newbon->SEResist[e+1]){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = effect_value; //Resist Chance break; } } @@ -2598,7 +2608,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_Screech: newbon->Screech = effect_value; break; - + + //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table + if (IsAISpellEffect) { + + //Non-Focused Effect to modify incomming spell damage by resist type. + case SE_FcSpellVulnerability: + ModVulnerability(base2, effect_value); + break; + } } } } diff --git a/zone/effects.cpp b/zone/effects.cpp index 329c69084..f2a8db2a6 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -55,9 +55,36 @@ int32 NPC::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { else value -= target->GetFcDamageAmtIncoming(this, spell_id)/spells[spell_id].buffduration; } - + value += dmg*SpellFocusDMG/100; + if (AI_HasSpellsEffects()){ + int16 chance = 0; + int ratio = 0; + + if (spells[spell_id].buffduration == 0) { + + chance += spellbonuses.CriticalSpellChance + spellbonuses.FrenziedDevastation; + + if (chance && MakeRandomInt(1,100) <= chance){ + + ratio += spellbonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncNoStack; + value += (value*ratio)/100; + entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_BLAST, GetCleanName(), itoa(-value)); + } + } + else { + + chance += spellbonuses.CriticalDoTChance; + + if (chance && MakeRandomInt(1,100) <= chance){ + + ratio += spellbonuses.DotCritDmgIncrease; + value += (value*ratio)/100; + } + } + } + return value; } @@ -254,6 +281,21 @@ int32 NPC::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); value += value*target->GetHealRate(spell_id, this)/100; } + + //Allow for critical heal chance if NPC is loading spell effect bonuses. + if (AI_HasSpellsEffects()){ + + if(spells[spell_id].buffduration < 1) { + + if(spellbonuses.CriticalHealChance && (MakeRandomInt(0,99) < spellbonuses.CriticalHealChance)) { + value = value*2; + entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_HEAL, GetCleanName(), itoa(value)); + } + } + else if(spellbonuses.CriticalHealOverTime && (MakeRandomInt(0,99) < spellbonuses.CriticalHealOverTime)) { + value = value*2; + } + } return value; } diff --git a/zone/mob.cpp b/zone/mob.cpp index 48452f26a..dcd204d0b 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4150,10 +4150,10 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if (spells[spell_id].effectid[i] == SE_SpellOnKill2) { - if (spells[spell_id].max[i] <= level) + if (IsValidSpell(spells[spell_id].base2[i]) && spells[spell_id].max[i] <= level) { if(MakeRandomInt(0,99) < spells[spell_id].base[i]) - SpellFinished(spells[spell_id].base2[i], this); + SpellFinished(spells[spell_id].base2[i], this, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); } } } @@ -4166,19 +4166,19 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) // Allow to check AA, items and buffs in all cases. Base2 = Spell to fire | Base1 = % chance | Base3 = min level for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) { - if(aabonuses.SpellOnKill[i] && (level >= aabonuses.SpellOnKill[i + 2])) { + if(aabonuses.SpellOnKill[i] && IsValidSpell(aabonuses.SpellOnKill[i]) && (level >= aabonuses.SpellOnKill[i + 2])) { if(MakeRandomInt(0, 99) < static_cast(aabonuses.SpellOnKill[i + 1])) - SpellFinished(aabonuses.SpellOnKill[i], this); + SpellFinished(aabonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } - if(itembonuses.SpellOnKill[i] && (level >= itembonuses.SpellOnKill[i + 2])){ + if(itembonuses.SpellOnKill[i] && IsValidSpell(itembonuses.SpellOnKill[i]) && (level >= itembonuses.SpellOnKill[i + 2])){ if(MakeRandomInt(0, 99) < static_cast(itembonuses.SpellOnKill[i + 1])) - SpellFinished(itembonuses.SpellOnKill[i], this); + SpellFinished(itembonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } - if(spellbonuses.SpellOnKill[i] && (level >= spellbonuses.SpellOnKill[i + 2])) { + if(spellbonuses.SpellOnKill[i] && IsValidSpell(spellbonuses.SpellOnKill[i]) && (level >= spellbonuses.SpellOnKill[i + 2])) { if(MakeRandomInt(0, 99) < static_cast(spellbonuses.SpellOnKill[i + 1])) - SpellFinished(spellbonuses.SpellOnKill[i], this); + SpellFinished(spellbonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } } @@ -4193,21 +4193,21 @@ bool Mob::TrySpellOnDeath() return false; for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { - if(IsClient() && aabonuses.SpellOnDeath[i]) { + if(IsClient() && aabonuses.SpellOnDeath[i] && IsValidSpell(aabonuses.SpellOnDeath[i])) { if(MakeRandomInt(0, 99) < static_cast(aabonuses.SpellOnDeath[i + 1])) { - SpellFinished(aabonuses.SpellOnDeath[i], this); + SpellFinished(aabonuses.SpellOnDeath[i], this, 10, 0, -1, spells[aabonuses.SpellOnDeath[i]].ResistDiff); } } - if(itembonuses.SpellOnDeath[i]) { + if(itembonuses.SpellOnDeath[i] && IsValidSpell(itembonuses.SpellOnDeath[i])) { if(MakeRandomInt(0, 99) < static_cast(itembonuses.SpellOnDeath[i + 1])) { - SpellFinished(itembonuses.SpellOnDeath[i], this); + SpellFinished(itembonuses.SpellOnDeath[i], this, 10, 0, -1, spells[itembonuses.SpellOnDeath[i]].ResistDiff); } } - if(spellbonuses.SpellOnDeath[i]) { + if(spellbonuses.SpellOnDeath[i] && IsValidSpell(spellbonuses.SpellOnDeath[i])) { if(MakeRandomInt(0, 99) < static_cast(spellbonuses.SpellOnDeath[i + 1])) { - SpellFinished(spellbonuses.SpellOnDeath[i], this); + SpellFinished(spellbonuses.SpellOnDeath[i], this, 10, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff); } } } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 4ecb9d221..8731dfaab 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -5675,7 +5675,7 @@ uint16 Mob::GetSpellEffectResistChance(uint16 spell_id) if(!IsValidSpell(spell_id)) return 0; - if (!aabonuses.SEResist[0] && !spellbonuses.SEResist[0] && !itembonuses.SEResist[0]) + if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1]) return 0; uint16 resist_chance = 0; From d3a9d509a832ecc1231c3cbcc385fe5d9cef4228 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sun, 27 Apr 2014 04:02:41 -0400 Subject: [PATCH 31/37] remove old sql --- .../required/2014_04_25_AISpellEffects.sql | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 utils/sql/git/required/2014_04_25_AISpellEffects.sql diff --git a/utils/sql/git/required/2014_04_25_AISpellEffects.sql b/utils/sql/git/required/2014_04_25_AISpellEffects.sql deleted file mode 100644 index a85b117bc..000000000 --- a/utils/sql/git/required/2014_04_25_AISpellEffects.sql +++ /dev/null @@ -1,50 +0,0 @@ --- TEST SQL -- - -ALTER TABLE `npc_types` ADD `npc_spells_effects_id` int( 11 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_spells_id`; - -SET FOREIGN_KEY_CHECKS=0; - --- ---------------------------- --- Table structure for `npc_spells_effects_entries` --- ---------------------------- -DROP TABLE IF EXISTS `npc_spells_effects_entries`; -CREATE TABLE `npc_spells_effects_entries` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `npc_spells_effects_id` int(11) NOT NULL DEFAULT '0', - `spell_effect_id` smallint(5) NOT NULL DEFAULT '0', - `minlevel` tinyint(3) unsigned NOT NULL, - `maxlevel` tinyint(3) unsigned NOT NULL, - `se_base` int(11) NOT NULL DEFAULT '0', - `se_limit` int(11) NOT NULL DEFAULT '0', - `se_max` int(11) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - UNIQUE KEY `spellsid_spellid` (`npc_spells_effects_id`,`spell_effect_id`) -) ENGINE=InnoDB AUTO_INCREMENT=18374 DEFAULT CHARSET=latin1; - --- ---------------------------- --- Records of npc_spells_effects_entries --- ---------------------------- -INSERT INTO `npc_spells_effects_entries` VALUES ('1', '1', '169', '0', '255', '10000', '-1', '0'); -INSERT INTO `npc_spells_effects_entries` VALUES ('2', '1', '168', '0', '255', '3999', '-1', '0'); -INSERT INTO `npc_spells_effects_entries` VALUES ('3', '2', '167', '0', '255', '98', '-1', '0'); - - - -SET FOREIGN_KEY_CHECKS=0; - --- ---------------------------- --- Table structure for `npc_spells_effects` --- ---------------------------- -DROP TABLE IF EXISTS `npc_spells_effects`; -CREATE TABLE `npc_spells_effects` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `name` tinytext, - `parent_list` int(11) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=1079 DEFAULT CHARSET=latin1; - --- ---------------------------- --- Records of npc_spells_effects --- ---------------------------- -INSERT INTO `npc_spells_effects` VALUES ('1', 'Critical', '0'); -INSERT INTO `npc_spells_effects` VALUES ('2', 'ParentTest', '1'); From cd9cf9f52f9b11d4337010e7bc3397b9d566293a Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sun, 27 Apr 2014 04:27:02 -0400 Subject: [PATCH 32/37] sql hot fix --- utils/sql/git/required/2014_04_27_AISpellEffects.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/sql/git/required/2014_04_27_AISpellEffects.sql b/utils/sql/git/required/2014_04_27_AISpellEffects.sql index 6a5645513..171ae6956 100644 --- a/utils/sql/git/required/2014_04_27_AISpellEffects.sql +++ b/utils/sql/git/required/2014_04_27_AISpellEffects.sql @@ -59,8 +59,8 @@ CREATE TABLE `npc_spells_effects_entries` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `npc_spells_effects_id` int(11) NOT NULL DEFAULT '0', `spell_effect_id` smallint(5) NOT NULL DEFAULT '0', - `minlevel` tinyint(3) unsigned NOT NULL, - `maxlevel` tinyint(3) unsigned NOT NULL, + `minlevel` tinyint(3) NOT NULL DEFAULT '0', + `maxlevel` tinyint(3) NOT NULL DEFAULT '255', `se_base` int(11) NOT NULL DEFAULT '0', `se_limit` int(11) NOT NULL DEFAULT '0', `se_max` int(11) NOT NULL DEFAULT '0', From d652cc8ea4f46be1c01495bc076aa41b35e87906 Mon Sep 17 00:00:00 2001 From: KimLS Date: Mon, 28 Apr 2014 13:10:11 -0700 Subject: [PATCH 33/37] Raycastmesh missing malloc, free, NULL refs --- zone/RaycastMesh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zone/RaycastMesh.cpp b/zone/RaycastMesh.cpp index b6635c7e6..34070d8da 100644 --- a/zone/RaycastMesh.cpp +++ b/zone/RaycastMesh.cpp @@ -1,6 +1,8 @@ #include "RaycastMesh.h" #include #include +#include +#include #include // This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do From 6ad4a95cc66b77441327a855a2526db5f68e81b7 Mon Sep 17 00:00:00 2001 From: KimLS Date: Mon, 28 Apr 2014 13:15:43 -0700 Subject: [PATCH 34/37] And of course memset too way to not include the whole error message the first time jerk --- zone/RaycastMesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/zone/RaycastMesh.cpp b/zone/RaycastMesh.cpp index 34070d8da..404961bf0 100644 --- a/zone/RaycastMesh.cpp +++ b/zone/RaycastMesh.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include // This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do From 649f363917c85560382ef5e4c09dc57aea79b571 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Mon, 28 Apr 2014 17:18:23 -0400 Subject: [PATCH 35/37] fix sql --- utils/sql/git/required/2014_04_27_AISpellEffects.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/sql/git/required/2014_04_27_AISpellEffects.sql b/utils/sql/git/required/2014_04_27_AISpellEffects.sql index 171ae6956..2ff0c49bd 100644 --- a/utils/sql/git/required/2014_04_27_AISpellEffects.sql +++ b/utils/sql/git/required/2014_04_27_AISpellEffects.sql @@ -49,6 +49,7 @@ INSERT INTO `npc_spells_effects` VALUES ('28', 'Increase Spell Vulnerability [Al INSERT INTO `npc_spells_effects` VALUES ('29', 'Decrease Spell Vulnerability [FR]', '0'); INSERT INTO `npc_spells_effects` VALUES ('30', 'Movement Speed', '0'); + SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- @@ -59,8 +60,8 @@ CREATE TABLE `npc_spells_effects_entries` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `npc_spells_effects_id` int(11) NOT NULL DEFAULT '0', `spell_effect_id` smallint(5) NOT NULL DEFAULT '0', - `minlevel` tinyint(3) NOT NULL DEFAULT '0', - `maxlevel` tinyint(3) NOT NULL DEFAULT '255', + `minlevel` tinyint(3) unsigned NOT NULL DEFAULT '0', + `maxlevel` tinyint(3) unsigned NOT NULL DEFAULT '255', `se_base` int(11) NOT NULL DEFAULT '0', `se_limit` int(11) NOT NULL DEFAULT '0', `se_max` int(11) NOT NULL DEFAULT '0', @@ -101,4 +102,3 @@ INSERT INTO `npc_spells_effects_entries` VALUES ('27', '27', '382', '0', '255', INSERT INTO `npc_spells_effects_entries` VALUES ('28', '28', '296', '0', '255', '1000', '-1', '0'); INSERT INTO `npc_spells_effects_entries` VALUES ('29', '29', '296', '0', '255', '-50', '2', '0'); INSERT INTO `npc_spells_effects_entries` VALUES ('30', '30', '3', '0', '255', '60', '0', '0'); - From c6b637e5a58985c231f4066fd0f3e813146dab9a Mon Sep 17 00:00:00 2001 From: KimLS Date: Tue, 29 Apr 2014 13:05:45 -0700 Subject: [PATCH 36/37] Changelog --- changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.txt b/changelog.txt index 92bb01e61..869ff369c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/29/2014 == +KLS: Implemented new map code based on some of Derision's earlier work. Old maps still work with this system and don't need to be regenerated. We're still working on a new azone solution for better/more efficient maps. + == 04/27/2014 == Kayen: Implemented new table 'npc_spells_effects' and 'npc_spells_effects_entires'. Implemented new field in 'npc_spell_effects_id' in npc_types. From a2e86ebec553bee465253bc9061f218c24842760 Mon Sep 17 00:00:00 2001 From: KimLS Date: Tue, 29 Apr 2014 17:49:17 -0700 Subject: [PATCH 37/37] Some compile fixes for older versions of C++ on windows --- zone/map.cpp | 4 ++-- zone/pathing.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zone/map.cpp b/zone/map.cpp index 088fc0c45..ccf9d0616 100644 --- a/zone/map.cpp +++ b/zone/map.cpp @@ -32,8 +32,8 @@ float Map::FindBestZ(Vertex &start, Vertex *result) const { result = &tmp; start.z += RuleI(Map, FindBestZHeightAdjust); - Vertex from = { start.x, start.y, start.z }; - Vertex to = { start.x, start.y, BEST_Z_INVALID }; + Vertex from(start.x, start.y, start.z); + Vertex to(start.x, start.y, BEST_Z_INVALID); float hit_distance; bool hit = false; diff --git a/zone/pathing.cpp b/zone/pathing.cpp index 4e5ae4cbd..06d59cf9c 100644 --- a/zone/pathing.cpp +++ b/zone/pathing.cpp @@ -1185,7 +1185,7 @@ bool PathManager::NoHazardsAccurate(Map::Vertex From, Map::Vertex To) stepy = (stepy / factor)*step_size; stepz = (stepz / factor)*step_size; - Map::Vertex TestPoint = { curx, cury, curz }; + Map::Vertex TestPoint(curx, cury, curz); float NewZ = zone->zonemap->FindBestZ(TestPoint, nullptr); if (ABS(NewZ - last_z) > 5.0f) { @@ -1204,7 +1204,7 @@ bool PathManager::NoHazardsAccurate(Map::Vertex From, Map::Vertex To) if (zone->watermap->InLiquid(TestPoint.x, TestPoint.y, NewZ)) { - Map::Vertex TestPointWater = { TestPoint.x, TestPoint.y, NewZ - 0.5f }; + Map::Vertex TestPointWater(TestPoint.x, TestPoint.y, NewZ - 0.5f); Map::Vertex TestPointWaterDest = TestPointWater; Map::Vertex hit; TestPointWaterDest.z -= 500;