Index: src/game/RandomMovementGenerator.cpp =================================================================== --- src/game/RandomMovementGenerator.cpp (revision 5789) +++ src/game/RandomMovementGenerator.cpp (working copy) @@ -21,69 +21,220 @@ #include "Opcodes.h" #include "RandomMovementGenerator.h" #include "DestinationHolderImp.h" +#include "VMapFactory.h" +#include "Map.h" -/* -Here interpolation is disabled by default due to it causing crashing -in some compile environments. -If your server can handle the small amount of lag this may cause -and you can build without experiencing a significant increase in crashes -then you may uncomment the following line to have correct random motion. -*/ -// uncomment now for wide testing purpose -#define USE_INTERPOLATION +/** + * Those define the size of the table of the ground/object heights around the spawn point, + * bigger they are, more time is needed to calculate. + */ +#define ANGLE_PRECISSION 8 +#define RANGE_PRECISSION 8 template<> void RandomMovementGenerator::Initialize(Creature &creature) { - float x,y,z,z2, wander_distance; - creature.GetRespawnCoord(x, y, z,NULL,&wander_distance); - uint32 mapid=creature.GetMapId(); + QueryResult *result = NULL; + bool cached = false; + // Try cache + result = WorldDatabase.PQuery("SELECT position_x, position_y, position_z FROM cache_random_movement_generator WHERE guid = '%u' ORDER BY point", creature.GetGUIDLow()); + if( result ) { + if(result->GetRowCount()==MAX_RAND_WAYPOINTS + 1) + { + for(unsigned int idx = 0; idx < MAX_RAND_WAYPOINTS + 1; ++idx) + { + Field *fields = result->Fetch(); + // Read from cache + i_waypoints[idx][0] = fields[0].GetFloat(); + i_waypoints[idx][1] = fields[1].GetFloat(); + i_waypoints[idx][2] = fields[2].GetFloat(); + result->NextRow(); + } + // Mark creature as loaded from cache + cached = true; + } + } + // If we couldn't load creature from cache, do all the calc + if(!cached) + { + // We need vmap manager, to make creatures walk properly over the objects + VMAP::IVMapManager* vmgr = VMAP::VMapFactory::createOrGetVMapManager(); + // To use new algorithm, vmaps are needed, if not, just fall back to the previous one + bool vmaps_enabled = vmgr->isLineOfSightCalcEnabled() && vmgr->isHeightCalcEnabled(); + if(vmaps_enabled) + { + float x,y,z,wander_distance; + float waypoints[(ANGLE_PRECISSION*RANGE_PRECISSION)+1][3]; + int waypoint_count = 1; - Map const* map = MapManager::Instance().GetBaseMap(mapid); - // Initialization is done in bulk. Dont use vamps for that (4. parameter = false). It is too costly when entering a new map grid - z2 = map->GetHeight(x,y,z, false); // use .map base surface height - if( fabs( z2 - z ) < 5 ) - z = z2; + creature.GetRespawnCoord(x, y, z,NULL,&wander_distance); + uint32 mapid=creature.GetMapId(); - i_nextMove = 1; - i_waypoints[0][0] = x; - i_waypoints[0][1] = y; - i_waypoints[0][2] = z; + Map const* map = MapManager::Instance().GetBaseMap(mapid); - bool is_water_ok = creature.isCanSwimOrFly(); - bool is_land_ok = creature.isCanWalkOrFly(); + bool is_water_ok = creature.isCanSwimOrFly(); + bool is_land_ok = creature.isCanWalkOrFly(); - for(unsigned int idx=1; idx < MAX_RAND_WAYPOINTS+1; ++idx) - { - const float angle = 2*M_PI*rand_norm(); - const float range = wander_distance*rand_norm(); + float step = wander_distance/RANGE_PRECISSION; - i_waypoints[idx][0] = x+ range * cos(angle); - i_waypoints[idx][1] = y+ range * sin(angle); + waypoints[0][0] = x; + waypoints[0][1] = y; + waypoints[0][2] = z; + // Build the table of ground/object heights around spawn point + for(int a = 0; a < ANGLE_PRECISSION; a++) + { + float angle = (2*M_PI/ANGLE_PRECISSION) * (float)a; + // We use the previous Z value as a reference point to find out the map height + float last_z = z; + for(int r = 0; r < RANGE_PRECISSION; r++) + { + float range = step * ((float) r + 1.0); + // Calculate the test around the spawn point + float px = x + range * cos(angle); + float py = y + range * sin(angle); + MaNGOS::NormalizeMapCoord(px); + MaNGOS::NormalizeMapCoord(py); + float map_height = map->GetHeight(px, py , last_z, true); + float obj_height; - // prevent invalid coordinates generation - MaNGOS::NormalizeMapCoord(i_waypoints[idx][0]); - MaNGOS::NormalizeMapCoord(i_waypoints[idx][1]); + // Calculate the object height + vmgr->getObjectHitPos(mapid, + px, py, last_z + 2.0f, + px, py, ((map_height!=VMAP_INVALID_HEIGHT_VALUE) ? (map_height) : (last_z-2.0f)), + px, py, obj_height, + 0.0f); - bool is_water = map->IsInWater(i_waypoints[idx][0],i_waypoints[idx][1],z); - // if generated wrong path just ignore - if( is_water && !is_water_ok || !is_water && !is_land_ok ) - { - i_waypoints[idx][0] = i_waypoints[idx-1][0]; - i_waypoints[idx][1] = i_waypoints[idx-1][1]; - i_waypoints[idx][2] = i_waypoints[idx-1][2]; - continue; - } + float pz = VMAP_INVALID_HEIGHT_VALUE; + if(obj_height>last_z-2.0f) + { + // Z value in the map will be the bigger of the two: map or object + pz = (((map_height) > (obj_height)) ? (map_height) : (obj_height)); + } - // Initialization is done in bulk. Dont use vamps for that (4. parameter = false). - // It is too costly when entering a new map grid, use .map base surface height - z2 = map->GetHeight(i_waypoints[idx][0],i_waypoints[idx][1],z, false); - if( fabs( z2 - z ) < 5 ) - z = z2; - i_waypoints[idx][2] = z; - } - i_nextMoveTime.Reset(urand(0, 10000-1)); // TODO: check the lower bound (it is probably too small) + if(pz!=VMAP_INVALID_HEIGHT_VALUE) + { + bool is_water = map->IsInWater(px,py,pz); + // If it is bad place for the creature (water for non water creature, ect), just mark it as invalid. + if(is_water && !is_water_ok || !is_water && !is_land_ok) + { + pz = VMAP_INVALID_HEIGHT_VALUE; + } + + // If point is not invalid, add to possible walk points + if(pz!=VMAP_INVALID_HEIGHT_VALUE) + { + last_z = pz; + waypoints[waypoint_count][0] = px; + waypoints[waypoint_count][1] = py; + waypoints[waypoint_count][2] = pz; + waypoint_count++; + } + } + } + } + + // Here, we calculate the walk points + i_nextMove = 1; + // First waypoint is always a spawn point, easy here. + i_waypoints[0][0] = x; + i_waypoints[0][1] = y; + i_waypoints[0][2] = z; + + for(unsigned int idx=1; idx < MAX_RAND_WAYPOINTS+1; ++idx) + { + bool goodPoint = false; + int r = urand(0, waypoint_count-1); + while(!goodPoint) + { + float dx = waypoints[r][0]; + float dy = waypoints[r][1]; + float dz = waypoints[r][2]; + // And check LOS + vmgr->getObjectHitPos(mapid, + i_waypoints[0][0], i_waypoints[0][1], i_waypoints[0][2] + creature.GetObjectSize()/2.0f, + dx, dy, dz + creature.GetObjectSize()/2.0f, + i_waypoints[idx][0], i_waypoints[idx][1], i_waypoints[idx][2], + creature.GetObjectSize()*2.0); + i_waypoints[idx][2] = i_waypoints[idx][2]- creature.GetObjectSize()/2.0f; + float x_dx = i_waypoints[idx][0] - i_waypoints[idx-1][0]; + float x_dy = i_waypoints[idx][1] - i_waypoints[idx-1][1]; + float x_dist = sqrt((x_dx*x_dx) + (x_dy*x_dy)); + float x_dz = i_waypoints[idx][2]-i_waypoints[idx-1][2]; + // Check if creature is not attempting to do >45 deg. walk + if(abs(x_dz)<=x_dist) + { + goodPoint = true; + } else { + // If yes, try next point + r = r + 1; + if(r==waypoint_count) + { + r = 0; + } + } + } + } + // Perform database dump of calculated waypoints to populate cache for future use + for(unsigned int idx=0; idx < MAX_RAND_WAYPOINTS+1; ++idx) + { + WorldDatabase.PExecuteLog("INSERT INTO cache_random_movement_generator (guid, point, position_x, position_y, position_z) VALUES ('%u','%u','%f','%f','%f')", + creature.GetGUIDLow(), idx, + i_waypoints[idx][0], i_waypoints[idx][1], i_waypoints[idx][2]); + } + } else { + // Fallback algorithm (previous one) + float x,y,z,z2, wander_distance; + creature.GetRespawnCoord(x, y, z,NULL,&wander_distance); + uint32 mapid=creature.GetMapId(); + + Map const* map = MapManager::Instance().GetBaseMap(mapid); + // Initialization is done in bulk. Dont use vamps for that (4. parameter = false). It is too costly when entering a new map grid + z2 = map->GetHeight(x,y,z, false); // use .map base surface height + if( fabs( z2 - z ) < 5 ) + z = z2; + + i_nextMove = 1; + i_waypoints[0][0] = x; + i_waypoints[0][1] = y; + i_waypoints[0][2] = z; + + bool is_water_ok = creature.isCanSwimOrFly(); + bool is_land_ok = creature.isCanWalkOrFly(); + + for(unsigned int idx=1; idx < MAX_RAND_WAYPOINTS+1; ++idx) + { + const float angle = 2*M_PI*rand_norm(); + const float range = wander_distance*rand_norm(); + + i_waypoints[idx][0] = x+ range * cos(angle); + i_waypoints[idx][1] = y+ range * sin(angle); + + // prevent invalid coordinates generation + MaNGOS::NormalizeMapCoord(i_waypoints[idx][0]); + MaNGOS::NormalizeMapCoord(i_waypoints[idx][1]); + + bool is_water = map->IsInWater(i_waypoints[idx][0],i_waypoints[idx][1],z); + // if generated wrong path just ignore + if( is_water && !is_water_ok || !is_water && !is_land_ok ) + { + i_waypoints[idx][0] = i_waypoints[idx-1][0]; + i_waypoints[idx][1] = i_waypoints[idx-1][1]; + i_waypoints[idx][2] = i_waypoints[idx-1][2]; + continue; + } + + // Initialization is done in bulk. Dont use vamps for that (4. parameter = false). + // It is too costly when entering a new map grid, use .map base surface height + z2 = map->GetHeight(i_waypoints[idx][0],i_waypoints[idx][1],z, false); + if( fabs( z2 - z ) < 5 ) + z = z2; + i_waypoints[idx][2] = z; + } + } + } + + i_nextMoveTime.Reset(urand(0, 10000-1)); // TODO: check the lower bound (it is probably too small) creature.StopMoving(); } @@ -105,10 +256,8 @@ if(creature.hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNDED)) return true; i_nextMoveTime.Update(diff); - #ifdef USE_INTERPOLATION CreatureTraveller traveller(creature); i_destinationHolder.UpdateTraveller(traveller, diff, false); - #endif if( i_nextMoveTime.Passed() ) { if( creature.IsStopped() ) @@ -120,9 +269,7 @@ creature.addUnitState(UNIT_STAT_ROAMING); CreatureTraveller traveller(creature); i_destinationHolder.SetDestination(traveller, x, y, z); - #ifndef USE_INTERPOLATION traveller.Relocation(x,y,z); - #endif i_nextMoveTime.Reset( i_destinationHolder.GetTotalTravelTime() ); } else