eqemu-server/zone/waypoints.cpp
2013-05-09 11:37:51 -04:00

1409 lines
38 KiB
C++

/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2004 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"
#ifdef _EQDEBUG
#include <iostream>
using namespace std;
#endif
//#include <iomanip>
#include <stdlib.h>
#include <math.h>
#include "npc.h"
#include "masterentity.h"
#include "NpcAI.h"
#include "map.h"
#include "watermap.h"
#include "../common/moremath.h"
#include "parser.h"
#include "StringIDs.h"
#include "../common/MiscFunctions.h"
#include "../common/rulesys.h"
#include "../common/features.h"
#include "QuestParserCollection.h"
struct wp_distance
{
float dist;
int index;
};
static inline float ABS(float x) {
if(x < 0)
return(-x);
return(x);
}
void NPC::AI_SetRoambox(float iDist, float iRoamDist, uint32 iDelay) {
AI_SetRoambox(iDist, GetX()+iRoamDist, GetX()-iRoamDist, GetY()+iRoamDist, GetY()-iRoamDist, iDelay);
}
void NPC::AI_SetRoambox(float iDist, float iMaxX, float iMinX, float iMaxY, float iMinY, uint32 iDelay) {
roambox_distance = iDist;
roambox_max_x = iMaxX;
roambox_min_x = iMinX;
roambox_max_y = iMaxY;
roambox_min_y = iMinY;
roambox_movingto_x = roambox_max_x + 1; // this will trigger a recalc
roambox_delay = iDelay;
}
void NPC::DisplayWaypointInfo(Client *c) {
c->Message(0, "Mob is on grid %d, in spawn group %d, on waypoint %d/%d",
GetGrid(),
GetSp2(),
GetCurWp(),
GetMaxWp() );
vector<wplist>::iterator cur, end;
cur = Waypoints.begin();
end = Waypoints.end();
for(; cur != end; cur++) {
c->Message(0,"Waypoint %d: (%.2f,%.2f,%.2f,%.2f) pause %d",
cur->index,
cur->x,
cur->y,
cur->z,
cur->heading,
cur->pause );
}
}
void NPC::StopWandering()
{ // stops a mob from wandering, takes him off grid and sends him back to spawn point
roamer=false;
CastToNPC()->SetGrid(0);
SendPosition();
mlog(QUESTS__PATHING, "Stop Wandering requested.");
return;
}
void NPC::ResumeWandering()
{ // causes wandering to continue - overrides waypoint pause timer and PauseWandering()
if(!IsNPC())
return;
if (GetGrid() != 0)
{
if (GetGrid() < 0)
{ // we were paused by a quest
AIwalking_timer->Disable();
SetGrid( 0 - GetGrid());
if (cur_wp==-1)
{ // got here by a MoveTo()
cur_wp=save_wp;
UpdateWaypoint(cur_wp); // have him head to last destination from here
}
mlog(QUESTS__PATHING, "Resume Wandering requested. Grid %d, wp %d", GetGrid(), cur_wp);
}
else if (AIwalking_timer->Enabled())
{ // we are at a waypoint paused normally
mlog(QUESTS__PATHING, "Resume Wandering on timed pause. Grid %d, wp %d", GetGrid(), cur_wp);
AIwalking_timer->Trigger(); // disable timer to end pause now
}
else
{
LogFile->write(EQEMuLog::Error, "NPC not paused - can't resume wandering: %lu", (unsigned long)GetNPCTypeID());
return;
}
if (cur_wp_x == GetX() && cur_wp_y == GetY())
{ // are we we at a waypoint? if so, trigger event and start to next
char temp[100];
itoa(cur_wp,temp,10); //do this before updating to next waypoint
CalculateNewWaypoint();
SetAppearance(eaStanding, false);
parse->EventNPC(EVENT_WAYPOINT_DEPART, this, nullptr, temp, 0);
} // if not currently at a waypoint, we continue on to the one we were headed to before the stop
}
else
{
LogFile->write(EQEMuLog::Error, "NPC not on grid - can't resume wandering: %lu", (unsigned long)GetNPCTypeID());
}
return;
}
void NPC::PauseWandering(int pausetime)
{ // causes wandering to stop but is resumable
// 0 pausetime means pause until resumed
// otherwise automatically resume when time is up
if (GetGrid() != 0)
{
DistractedFromGrid = true;
mlog(QUESTS__PATHING, "Paused Wandering requested. Grid %d. Resuming in %d ms (0=not until told)", GetGrid(), pausetime);
SendPosition();
if (pausetime<1)
{ // negative grid number stops him dead in his tracks until ResumeWandering()
SetGrid( 0 - GetGrid());
}
else
{ // specified waiting time, he'll resume after that
AIwalking_timer->Start(pausetime*1000); // set the timer
}
} else {
LogFile->write(EQEMuLog::Error, "NPC not on grid - can't pause wandering: %lu", (unsigned long)GetNPCTypeID());
}
return;
}
void NPC::MoveTo(float mtx, float mty, float mtz, float mth, bool saveguardspot)
{ // makes mob walk to specified location
if (IsNPC() && GetGrid() != 0)
{ // he is on a grid
if (GetGrid() < 0)
{ // currently stopped by a quest command
SetGrid( 0 - GetGrid()); // get him moving again
mlog(AI__WAYPOINTS, "MoveTo during quest wandering. Canceling quest wandering and going back to grid %d when MoveTo is done.", GetGrid());
}
AIwalking_timer->Disable(); // disable timer in case he is paused at a wp
if (cur_wp>=0)
{ // we've not already done a MoveTo()
save_wp=cur_wp; // save the current waypoint
cur_wp=-1; // flag this move as quest controlled
}
mlog(AI__WAYPOINTS, "MoveTo (%.3f, %.3f, %.3f), pausing regular grid wandering. Grid %d, save_wp %d", mtx, mty, mtz, -GetGrid(), save_wp);
}
else
{ // not on a grid
roamer=true;
save_wp=0;
cur_wp=-2; // flag as quest controlled w/no grid
mlog(AI__WAYPOINTS, "MoveTo (%.3f, %.3f, %.3f) without a grid.", mtx, mty, mtz);
}
if (saveguardspot)
{
guard_x = mtx;
guard_y = mty;
guard_z = mtz;
guard_heading = mth;
if(guard_heading == 0)
guard_heading = 0.0001; //hack to make IsGuarding simpler
if(guard_heading == -1)
guard_heading = this->CalculateHeadingToTarget(mtx, mty);
mlog(AI__WAYPOINTS, "Setting guard position to (%.3f, %.3f, %.3f)", guard_x, guard_y, guard_z);
}
cur_wp_x = mtx;
cur_wp_y = mty;
cur_wp_z = mtz;
cur_wp_pause = 0;
cur_wp_heading = mth;
pLastFightingDelayMoving = 0;
if(AIwalking_timer->Enabled())
AIwalking_timer->Start(100);
}
void NPC::UpdateWaypoint(int wp_index)
{
if(wp_index >= static_cast<int>(Waypoints.size())) {
mlog(AI__WAYPOINTS, "Update to waypoint %d failed. Not found.", wp_index);
return;
}
vector<wplist>::iterator cur;
cur = Waypoints.begin();
cur += wp_index;
cur_wp_x = cur->x;
cur_wp_y = cur->y;
cur_wp_z = cur->z;
cur_wp_pause = cur->pause;
cur_wp_heading = cur->heading;
mlog(AI__WAYPOINTS, "Next waypoint %d: (%.3f, %.3f, %.3f, %.3f)", wp_index, cur_wp_x, cur_wp_y, cur_wp_z, cur_wp_heading);
//fix up pathing Z
if(zone->HasMap() && RuleB(Map, FixPathingZAtWaypoints))
{
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);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
if( (newz > -2000) && ABS(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaWaypoint))
cur_wp_z = newz + 1;
}
}
}
void NPC::CalculateNewWaypoint()
{
int old_wp = cur_wp;
bool reached_end = false;
bool reached_beginning = false;
if (cur_wp == max_wp)
reached_end = true;
if (cur_wp == 0)
reached_beginning = true;
switch(wandertype)
{
case 0: //circle
{
if (reached_end)
cur_wp = 0;
else
cur_wp = cur_wp + 1;
break;
}
case 1: //10 closest
{
list<wplist> closest;
GetClosestWaypoint(closest, 10, GetX(), GetY(), GetZ());
list<wplist>::iterator iter = closest.begin();
if(closest.size() != 0)
{
int idx = MakeRandomInt(0, closest.size() - 1);
iter = closest.begin();
for(int i = 0; i < idx; ++i)
{
iter++;
}
cur_wp = (*iter).index;
}
break;
}
case 2: //random
{
cur_wp = MakeRandomInt(0, Waypoints.size() - 1);
if(cur_wp == old_wp)
{
if(cur_wp == (Waypoints.size() - 1))
{
if(cur_wp > 0)
{
cur_wp--;
}
}
else if(cur_wp == 0)
{
if((Waypoints.size() - 1) > 0)
{
cur_wp++;
}
}
}
break;
}
case 3: //patrol
{
if(reached_end)
patrol = 1;
else if(reached_beginning)
patrol = 0;
if(patrol == 1)
cur_wp = cur_wp - 1;
else
cur_wp = cur_wp + 1;
break;
}
case 4: //goto the end and depop with spawn timer
case 6: //goto the end and depop without spawn timer
{
cur_wp = cur_wp + 1;
break;
}
case 5: //pick random closest 5 and pick one that's in sight
{
list<wplist> closest;
GetClosestWaypoint(closest, 5, GetX(), GetY(), GetZ());
list<wplist>::iterator iter = closest.begin();
while(iter != closest.end())
{
if(CheckLosFN((*iter).x, (*iter).y, (*iter).z, GetSize()))
{
iter++;
}
else
{
iter = closest.erase(iter);
}
}
if(closest.size() != 0)
{
int idx = MakeRandomInt(0, closest.size() - 1);
iter = closest.begin();
for(int i = 0; i < idx; ++i)
{
iter++;
}
cur_wp = (*iter).index;
}
break;
}
}
tar_ndx = 52;
// Preserve waypoint setting for quest controlled NPCs
if (cur_wp < 0)
cur_wp = old_wp;
// Check to see if we need to update the waypoint.
if (cur_wp != old_wp)
UpdateWaypoint(cur_wp);
}
bool wp_distance_pred(const wp_distance& left, const wp_distance& right)
{
return left.dist < right.dist;
}
void NPC::GetClosestWaypoint(list<wplist> &wp_list, int count, float m_x, float m_y, float m_z)
{
wp_list.clear();
if(Waypoints.size() <= count)
{
for(int i = 0; i < Waypoints.size(); ++i)
{
wp_list.push_back(Waypoints[i]);
}
return;
}
list<wp_distance> distances;
for(int i = 0; i < Waypoints.size(); ++i)
{
float cur_x = (Waypoints[i].x - m_x);
cur_x *= cur_x;
float cur_y = (Waypoints[i].y - m_y);
cur_y *= cur_y;
float cur_z = (Waypoints[i].z - m_z);
cur_z *= cur_z;
float cur_dist = cur_x + cur_y + cur_z;
wp_distance w_dist;
w_dist.dist = cur_dist;
w_dist.index = i;
distances.push_back(w_dist);
}
distances.sort(wp_distance_pred);
list<wp_distance>::iterator iter = distances.begin();
for(int i = 0; i < count; ++i)
{
wp_list.push_back(Waypoints[(*iter).index]);
iter++;
}
}
void NPC::SetWaypointPause()
{
//Declare time to wait on current WP
if (cur_wp_pause == 0) {
AIwalking_timer->Start(100);
}
else
{
switch (pausetype)
{
case 0: //Random Half
AIwalking_timer->Start((cur_wp_pause - MakeRandomInt(0, cur_wp_pause-1)/2)*1000);
break;
case 1: //Full
AIwalking_timer->Start(cur_wp_pause*1000);
break;
case 2: //Random Full
AIwalking_timer->Start(MakeRandomInt(0, cur_wp_pause-1)*1000);
break;
}
}
}
void NPC::SaveGuardSpot(bool iClearGuardSpot) {
if (iClearGuardSpot) {
mlog(AI__WAYPOINTS, "Clearing guard order.");
guard_x = 0;
guard_y = 0;
guard_z = 0;
guard_heading = 0;
}
else {
guard_x = x_pos;
guard_y = y_pos;
guard_z = z_pos;
guard_heading = heading;
if(guard_heading == 0)
guard_heading = 0.0001; //hack to make IsGuarding simpler
mlog(AI__WAYPOINTS, "Setting guard position to (%.3f, %.3f, %.3f)", guard_x, guard_y, guard_z);
}
}
void NPC::NextGuardPosition() {
if (!CalculateNewPosition2(guard_x, guard_y, guard_z, GetMovespeed())) {
SetHeading(guard_heading);
mlog(AI__WAYPOINTS, "Unable to move to next guard position. Probably rooted.");
}
else if((x_pos == guard_x) && (y_pos == guard_y) && (z_pos == guard_z))
{
if(moved)
{
moved=false;
SetMoving(false);
SendPosition();
}
}
}
/*
// we need this for charmed NPCs
void Mob::SaveSpawnSpot() {
spawn_x = x_pos;
spawn_y = y_pos;
spawn_z = z_pos;
spawn_heading = heading;
}*/
/*float Mob::CalculateDistanceToNextWaypoint() {
return CalculateDistance(cur_wp_x, cur_wp_y, cur_wp_z);
}*/
float Mob::CalculateDistance(float x, float y, float z) {
return (float)sqrtf( ((x_pos-x)*(x_pos-x)) + ((y_pos-y)*(y_pos-y)) + ((z_pos-z)*(z_pos-z)) );
}
/*
uint8 NPC::CalculateHeadingToNextWaypoint() {
return CalculateHeadingToTarget(cur_wp_x, cur_wp_y);
}
*/
float Mob::CalculateHeadingToTarget(float in_x, float in_y) {
float angle;
if (in_x-x_pos > 0)
angle = - 90 + atan((float)(in_y-y_pos) / (float)(in_x-x_pos)) * 180 / M_PI;
else if (in_x-x_pos < 0)
angle = + 90 + atan((float)(in_y-y_pos) / (float)(in_x-x_pos)) * 180 / M_PI;
else // Added?
{
if (in_y-y_pos > 0)
angle = 0;
else
angle = 180;
}
if (angle < 0)
angle += 360;
if (angle > 360)
angle -= 360;
return (256*(360-angle)/360.0f);
}
bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, bool checkZ) {
if(GetID()==0)
return true;
_ZP(Mob_CalculateNewPosition2);
if ((x_pos-x == 0) && (y_pos-y == 0)) {//spawn is at target coords
if(z_pos-z != 0) {
z_pos = z;
mlog(AI__WAYPOINTS, "Calc Position2 (%.3f, %.3f, %.3f): Jumping pure Z.", x, y, z);
return true;
}
mlog(AI__WAYPOINTS, "Calc Position2 (%.3f, %.3f, %.3f) inWater=%d: We are there.", x, y, z, inWater);
return false;
}
else if ((ABS(x_pos - x) < 0.1) && (ABS(y_pos - y) < 0.1))
{
mlog(AI__WAYPOINTS, "Calc Position2 (%.3f, %.3f, %.3f): X/Y difference <0.1, Jumping to target.", x, y, z);
x_pos = x;
y_pos = y;
z_pos = z;
return true;
}
int compare_steps = IsBoat() ? 1 : 20;
if(tar_ndx < compare_steps && tarx==x && tary==y){
x_pos = x_pos + tar_vx*tar_vector;
y_pos = y_pos + tar_vy*tar_vector;
z_pos = z_pos + tar_vz*tar_vector;
mlog(AI__WAYPOINTS, "Calculating new position2 to (%.3f, %.3f, %.3f), old vector (%.3f, %.3f, %.3f)", x, y, z, tar_vx, tar_vy, tar_vz);
uint8 NPCFlyMode = 0;
if(IsNPC()) {
if(CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2)
NPCFlyMode = 1;
}
//fix up pathing Z
if(!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving))
{
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);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
if( (newz > -2000) && ABS(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check.
{
if((ABS(x - x_pos) < 0.5) && (ABS(y - y_pos) < 0.5))
{
if(ABS(z-z_pos) <= RuleR(Map, FixPathingZMaxDeltaMoving))
z_pos = z;
else
z_pos = newz + 1;
}
else
z_pos = newz + 1;
}
}
}
tar_ndx++;
return true;
}
if (tar_ndx>50) {
tar_ndx--;
} else {
tar_ndx=0;
}
tarx=x;
tary=y;
tarz=z;
float nx = this->x_pos;
float ny = this->y_pos;
float nz = this->z_pos;
// float nh = this->heading;
tar_vx = x - nx;
tar_vy = y - ny;
tar_vz = z - nz;
//pRunAnimSpeed = (int8)(speed*NPC_RUNANIM_RATIO);
//speed *= NPC_SPEED_MULTIPLIER;
mlog(AI__WAYPOINTS, "Calculating new position2 to (%.3f, %.3f, %.3f), new vector (%.3f, %.3f, %.3f) rate %.3f, RAS %d", x, y, z, tar_vx, tar_vy, tar_vz, speed, pRunAnimSpeed);
// --------------------------------------------------------------------------
// 2: get unit vector
// --------------------------------------------------------------------------
float mag = sqrtf (tar_vx*tar_vx + tar_vy*tar_vy + tar_vz*tar_vz);
tar_vector = speed / mag;
// mob move fix
int numsteps = (int) ( mag * 20 / speed) + 1;
// mob move fix
if (numsteps<20)
{
if (numsteps>1)
{
tar_vector=1.0f ;
tar_vx = tar_vx/numsteps;
tar_vy = tar_vy/numsteps;
tar_vz = tar_vz/numsteps;
x_pos = x_pos + tar_vx;
y_pos = y_pos + tar_vy;
z_pos = z_pos + tar_vz;
tar_ndx=22-numsteps;
heading = CalculateHeadingToTarget(x, y);
mlog(AI__WAYPOINTS, "Next position2 (%.3f, %.3f, %.3f) (%d steps)", x_pos, y_pos, z_pos, numsteps);
}
else
{
x_pos = x;
y_pos = y;
z_pos = z;
mlog(AI__WAYPOINTS, "Only a single step to get there... jumping.");
}
}
else {
tar_vector/=20;
x_pos = x_pos + tar_vx*tar_vector;
y_pos = y_pos + tar_vy*tar_vector;
z_pos = z_pos + tar_vz*tar_vector;
heading = CalculateHeadingToTarget(x, y);
mlog(AI__WAYPOINTS, "Next position2 (%.3f, %.3f, %.3f) (%d steps)", x_pos, y_pos, z_pos, numsteps);
}
uint8 NPCFlyMode = 0;
if(IsNPC()) {
if(CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2)
NPCFlyMode = 1;
}
//fix up pathing Z
if(!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving)) {
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);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
if( (newz > -2000) && ABS(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check.
{
if(ABS(x - x_pos) < 0.5 && ABS(y - y_pos) < 0.5)
{
if(ABS(z - z_pos) <= RuleR(Map, FixPathingZMaxDeltaMoving))
z_pos = z;
else
z_pos = newz + 1;
}
else
z_pos = newz+1;
}
}
}
SetMoving(true);
moved=true;
delta_x=x_pos-nx;
delta_y=y_pos-ny;
delta_z=z_pos-nz;
delta_heading=0;
if (IsClient())
SendPosUpdate(1);
else
SendPosUpdate();
SetAppearance(eaStanding, false);
pLastChange = Timer::GetCurrentTime();
return true;
}
bool Mob::CalculateNewPosition2(float x, float y, float z, float speed, bool checkZ) {
if(IsNPC() || IsClient() || IsPet()) {
pRunAnimSpeed = (int8)(speed*NPC_RUNANIM_RATIO);
speed *= NPC_SPEED_MULTIPLIER;
}
return MakeNewPositionAndSendUpdate(x, y, z, speed, checkZ);
}
bool Mob::CalculateNewPosition(float x, float y, float z, float speed, bool checkZ) {
if(GetID()==0)
return true;
_ZP(Mob_CalculateNewPosition);
float nx = x_pos;
float ny = y_pos;
float nz = z_pos;
// float nh = heading;
// if NPC is rooted
if (speed == 0.0) {
SetHeading(CalculateHeadingToTarget(x, y));
if(moved){
SendPosition();
SetMoving(false);
moved=false;
}
SetRunAnimSpeed(0);
mlog(AI__WAYPOINTS, "Rooted while calculating new position to (%.3f, %.3f, %.3f)", x, y, z);
return true;
}
float old_test_vector=test_vector;
tar_vx = x - nx;
tar_vy = y - ny;
tar_vz = z - nz;
if (tar_vx == 0 && tar_vy == 0)
return false;
pRunAnimSpeed = (uint8)(speed*NPC_RUNANIM_RATIO);
speed *= NPC_SPEED_MULTIPLIER;
mlog(AI__WAYPOINTS, "Calculating new position to (%.3f, %.3f, %.3f) vector (%.3f, %.3f, %.3f) rate %.3f RAS %d", x, y, z, tar_vx, tar_vy, tar_vz, speed, pRunAnimSpeed);
// --------------------------------------------------------------------------
// 2: get unit vector
// --------------------------------------------------------------------------
test_vector=sqrtf (x*x + y*y + z*z);
tar_vector = speed / sqrtf (tar_vx*tar_vx + tar_vy*tar_vy + tar_vz*tar_vz);
heading = CalculateHeadingToTarget(x, y);
if (tar_vector >= 1.0) {
x_pos = x;
y_pos = y;
z_pos = z;
mlog(AI__WAYPOINTS, "Close enough, jumping to waypoint");
}
else {
x_pos = x_pos + tar_vx*tar_vector;
y_pos = y_pos + tar_vy*tar_vector;
z_pos = z_pos + tar_vz*tar_vector;
mlog(AI__WAYPOINTS, "Next position (%.3f, %.3f, %.3f)", x_pos, y_pos, z_pos);
}
uint8 NPCFlyMode = 0;
if(IsNPC()) {
if(CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2)
NPCFlyMode = 1;
}
//fix up pathing Z
if(!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving))
{
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);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
if( (newz > -2000) && ABS(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check.
{
if(ABS(x - x_pos) < 0.5 && ABS(y - y_pos) < 0.5)
{
if(ABS(z - z_pos) <= RuleR(Map, FixPathingZMaxDeltaMoving))
z_pos = z;
else
z_pos = newz + 1;
}
else
z_pos = newz+1;
}
}
}
//OP_MobUpdate
if((old_test_vector!=test_vector) || tar_ndx>20){ //send update
tar_ndx=0;
this->SetMoving(true);
moved=true;
delta_x=(x_pos-nx);
delta_y=(y_pos-ny);
delta_z=(z_pos-nz);
delta_heading=0;//(heading-nh)*8;
SendPosUpdate();
}
tar_ndx++;
// now get new heading
SetAppearance(eaStanding, false); // make sure they're standing
pLastChange = Timer::GetCurrentTime();
return true;
}
void NPC::AssignWaypoints(int32 grid) {
if(grid == 0)
return; //grid ID 0 not supported
if(grid < 0) {
// Allow setting negative grid values for pausing pathing
this->CastToNPC()->SetGrid(grid);
return;
}
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
bool GridErr = false, WPErr = false;
Waypoints.clear();
// Retrieve the wander and pause types for this grid
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `type`,`type2` FROM `grid` WHERE `id`=%i AND `zoneid`=%i",grid,zone->GetZoneID()),errbuf, &result))
{
if((row = mysql_fetch_row(result)))
{
if(row[0] != 0)
wandertype = atoi(row[0]);
else
wandertype = 0;
if(row[1] != 0)
pausetype = atoi(row[1]);
else
pausetype = 0;
}
else // No grid record found in this zone for the given ID
GridErr = true;
mysql_free_result(result);
}
else // DB query error!
{
GridErr = true;
LogFile->write(EQEMuLog::Error, "MySQL Error while trying to assign grid %u to mob %s: %s", grid, name, errbuf);
}
safe_delete_array(query);
if(!GridErr)
{
this->CastToNPC()->SetGrid(grid); // Assign grid number
adverrorinfo = 7561;
// Retrieve all waypoints for this grid
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `x`,`y`,`z`,`pause`,`heading` FROM grid_entries WHERE `gridid`=%i AND `zoneid`=%i ORDER BY `number`",grid,zone->GetZoneID()),errbuf,&result))
{
roamer = true;
max_wp = -1; // Initialize it; will increment it for each waypoint successfully added to the list
adverrorinfo = 7564;
while((row = mysql_fetch_row(result)))
{
if(row[0] != 0 && row[1] != 0 && row[2] != 0 && row[3] != 0)
{
wplist newwp;
newwp.index = ++max_wp;
newwp.x = atof(row[0]);
newwp.y = atof(row[1]);
newwp.z = atof(row[2]);
if(zone->HasMap() && RuleB(Map, FixPathingZWhenLoading) )
{
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);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
if( (newz > -2000) && ABS(newz-dest.z) < RuleR(Map, FixPathingZMaxDeltaLoading))
newwp.z = newz + 1;
}
}
newwp.pause = atoi(row[3]);
newwp.heading = atof(row[4]);
Waypoints.push_back(newwp);
}
}
mysql_free_result(result);
}
else // DB query error!
{
WPErr = true;
LogFile->write(EQEMuLog::Error, "MySQL Error while trying to assign waypoints from grid %u to mob %s: %s", grid, name, errbuf);
}
safe_delete_array(query);
} // end if (!GridErr)
if(Waypoints.size() < 2) {
roamer = false;
} else if(!GridErr && !WPErr) {
UpdateWaypoint(0);
SetWaypointPause();
if (wandertype == 1 || wandertype == 2 || wandertype == 5)
CalculateNewWaypoint();
} else {
roamer = false;
}
}
void Mob::SendTo(float new_x, float new_y, float new_z) {
x_pos = new_x;
y_pos = new_y;
z_pos = new_z;
mlog(AI__WAYPOINTS, "Sent To (%.3f, %.3f, %.3f)", new_x, new_y, new_z);
if(flymode == FlyMode1)
return;
//fix up pathing Z, this shouldent be needed IF our waypoints
//are corrected instead
if(zone->HasMap() && RuleB(Map, FixPathingZOnSendTo) )
{
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);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
if( (newz > -2000) && ABS(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaSendTo)) // Sanity check.
z_pos = newz + 1;
}
}
else
z_pos += 0.1;
}
void Mob::SendToFixZ(float new_x, float new_y, float new_z) {
x_pos = new_x;
y_pos = new_y;
z_pos = new_z + 0.1;
//fix up pathing Z, this shouldent be needed IF our waypoints
//are corrected instead
if(zone->HasMap() && RuleB(Map, FixPathingZOnSendTo))
{
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);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
if( (newz > -2000) && ABS(newz-dest.z) < RuleR(Map, FixPathingZMaxDeltaSendTo)) // Sanity check.
z_pos = newz + 1;
}
}
}
int ZoneDatabase::GetHighestGrid(uint32 zoneid) {
char *query = 0;
char errbuff[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
int res = 0;
if (RunQuery(query, MakeAnyLenString(&query,
"SELECT COALESCE(MAX(id), 0) FROM grid WHERE zoneid = %i",
zoneid),errbuff,&result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
res = atoi( row[0] );
}
mysql_free_result(result);
} else {
LogFile->write(EQEMuLog::Error, "Error in GetHighestGrid query '%s': %s", query, errbuff);
safe_delete_array(query);
}
return(res);
}
uint8 ZoneDatabase::GetGridType2(uint32 grid, uint16 zoneid) {
char *query = 0;
char errbuff[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
int type2 = 0;
if (RunQuery(query, MakeAnyLenString(&query,"SELECT type2 from grid where id = %i and zoneid = %i",grid,zoneid),errbuff,&result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
type2 = atoi( row[0] );
}
mysql_free_result(result);
} else {
LogFile->write(EQEMuLog::Error, "Error in GetGridType2 query '%s': %s", query, errbuff);
safe_delete_array(query);
}
return(type2);
}
bool ZoneDatabase::GetWaypoints(uint32 grid, uint16 zoneid, uint32 num, wplist* wp) {
_CP(Database_GetWaypoints);
char *query = 0;
char errbuff[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
if (RunQuery(query, MakeAnyLenString(&query,"SELECT x, y, z, pause, heading from grid_entries where gridid = %i and number = %i and zoneid = %i",grid,num,zoneid),errbuff,&result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
if ( wp ) {
wp->x = atof( row[0] );
wp->y = atof( row[1] );
wp->z = atof( row[2] );
wp->pause = atoi( row[3] );
wp->heading = atof( row[4] );
}
mysql_free_result(result);
return true;
}
mysql_free_result(result);
}
else {
LogFile->write(EQEMuLog::Error, "Error in GetWaypoints query '%s': %s", query, errbuff);
safe_delete_array(query);
}
return false;
}
void ZoneDatabase::AssignGrid(Client *client, float x, float y, uint32 grid)
{
char *query = 0;
char errbuf[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
int matches = 0, fuzzy = 0, spawn2id = 0;
uint32 affected_rows;
float dbx = 0, dby = 0;
// looks like most of the stuff in spawn2 is straight integers
// so let's try that first
if(!RunQuery(
query,
MakeAnyLenString(
&query,
"SELECT id,x,y FROM spawn2 WHERE zone='%s' AND x=%i AND y=%i",
zone->GetShortName(), (int)x, (int)y
),
errbuf,
&result
)) {
LogFile->write(EQEMuLog::Error, "Error querying spawn2 '%s': '%s'", query, errbuf);
return;
}
safe_delete_array(query);
// how much it's allowed to be off by
#define _GASSIGN_TOLERANCE 1.0
if(!(matches = mysql_num_rows(result))) // try a fuzzy match if that didn't find it
{
mysql_free_result(result);
if(!RunQuery(
query,
MakeAnyLenString(
&query,
"SELECT id,x,y FROM spawn2 WHERE zone='%s' AND "
"ABS( ABS(x) - ABS(%f) ) < %f AND "
"ABS( ABS(y) - ABS(%f) ) < %f",
zone->GetShortName(), x, _GASSIGN_TOLERANCE, y, _GASSIGN_TOLERANCE
),
errbuf,
&result
)) {
LogFile->write(EQEMuLog::Error, "Error querying fuzzy spawn2 '%s': '%s'", query, errbuf);
return;
}
safe_delete_array(query);
fuzzy = 1;
if(!(matches = mysql_num_rows(result)))
mysql_free_result(result);
}
if(matches)
{
if(matches > 1)
{
client->Message(0, "ERROR: Unable to assign grid - multiple spawn2 rows match");
mysql_free_result(result);
}
else
{
row = mysql_fetch_row(result);
spawn2id = atoi(row[0]);
dbx = atof(row[1]);
dby = atof(row[2]);
if(!RunQuery(
query,
MakeAnyLenString(
&query,
"UPDATE spawn2 SET pathgrid = %d WHERE id = %d", grid, spawn2id
),
errbuf,
&result,
&affected_rows
)) {
LogFile->write(EQEMuLog::Error, "Error updating spawn2 '%s': '%s'", query, errbuf);
return;
}
if(affected_rows == 1)
{
if(client) client->LogSQL(query);
if(fuzzy)
{
float difference;
difference = sqrtf(pow(fabs(x-dbx),2) + pow(fabs(y-dby),2));
client->Message(0,
"Grid assign: spawn2 id = %d updated - fuzzy match: deviation %f",
spawn2id, difference
);
}
else
{
client->Message(0, "Grid assign: spawn2 id = %d updated - exact match", spawn2id);
}
}
else
{
client->Message(0, "ERROR: found spawn2 id %d but the update query failed", spawn2id);
}
}
}
else
{
client->Message(0, "ERROR: Unable to assign grid - can't find it in spawn2");
}
}
/******************
* ModifyGrid - Either adds an empty grid, or removes a grid and all its waypoints, for a particular zone.
* remove: TRUE if we are deleting the specified grid, FALSE if we are adding it
* id: The ID# of the grid to add or delete
* type,type2: The type and type2 values for the grid being created (ignored if grid is being deleted)
* zoneid: The ID number of the zone the grid is being created/deleted in
*/
void ZoneDatabase::ModifyGrid(Client *c, bool remove, uint32 id, uint8 type, uint8 type2, uint16 zoneid) {
char *query = 0;
char errbuf[MYSQL_ERRMSG_SIZE];
if (!remove)
{
if(!RunQuery(query, MakeAnyLenString(&query,"INSERT INTO grid(id,zoneid,type,type2) VALUES(%i,%i,%i,%i)",id,zoneid,type,type2), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error creating grid entry '%s': '%s'", query, errbuf);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
}
else
{
if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid where id=%i",id), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error deleting grid '%s': '%s'", query, errbuf);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
query = 0;
if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid_entries WHERE zoneid=%i AND gridid=%i",zoneid,id), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error deleting grid entries '%s': '%s'", query, errbuf);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
}
} /*** END ZoneDatabase::ModifyGrid() ***/
/**************************************
* AddWP - Adds a new waypoint to a specific grid for a specific zone.
*/
void ZoneDatabase::AddWP(Client *c, uint32 gridid, uint32 wpnum, float xpos, float ypos, float zpos, uint32 pause, uint16 zoneid, float heading)
{
char *query = 0;
char errbuf[MYSQL_ERRMSG_SIZE];
if(!RunQuery(query,MakeAnyLenString(&query,"INSERT INTO grid_entries (gridid,zoneid,`number`,x,y,z,pause,heading) values (%i,%i,%i,%f,%f,%f,%i,%f)",gridid,zoneid,wpnum,xpos,ypos,zpos,pause,heading), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error adding waypoint '%s': '%s'", query, errbuf);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
} /*** END ZoneDatabase::AddWP() ***/
/**********
* ModifyWP() has been obsoleted. The #wp command either uses AddWP() or DeleteWaypoint()
***********/
/******************
* DeleteWaypoint - Removes a specific waypoint from the grid
* grid_id: The ID number of the grid whose wp is being deleted
* wp_num: The number of the waypoint being deleted
* zoneid: The ID number of the zone that contains the waypoint being deleted
*/
void ZoneDatabase::DeleteWaypoint(Client *c, uint32 grid_num, uint32 wp_num, uint16 zoneid)
{
char *query=0;
char errbuf[MYSQL_ERRMSG_SIZE];
if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid_entries where gridid=%i and zoneid=%i and `number`=%i",grid_num,zoneid,wp_num), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error deleting waypoint '%s': '%s'", query, errbuf);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
} /*** END ZoneDatabase::DeleteWaypoint() ***/
/******************
* AddWPForSpawn - Used by the #wpadd command - for a given spawn, this will add a new waypoint to whatever grid that spawn is assigned to.
* If there is currently no grid assigned to the spawn, a new grid will be created using the next available Grid ID number for the zone
* the spawn is in.
* Returns 0 if the function didn't have to create a new grid. If the function had to create a new grid for the spawn, then the ID of
* the created grid is returned.
*/
uint32 ZoneDatabase::AddWPForSpawn(Client *c, uint32 spawn2id, float xpos, float ypos, float zpos, uint32 pause, int type1, int type2, uint16 zoneid, float heading) {
char *query = 0;
uint32 grid_num, // The grid number the spawn is assigned to (if spawn has no grid, will be the grid number we end up creating)
next_wp_num; // The waypoint number we should be assigning to the new waypoint
bool CreatedNewGrid; // Did we create a new grid in this function?
MYSQL_RES *result;
MYSQL_ROW row;
char errbuf[MYSQL_ERRMSG_SIZE];
// See what grid number our spawn is assigned
if(RunQuery(query, MakeAnyLenString(&query,"SELECT pathgrid FROM spawn2 WHERE id=%i",spawn2id),errbuf,&result))
{
safe_delete_array(query);
if(mysql_num_rows(result) > 0)
{
row = mysql_fetch_row(result);
grid_num = atoi(row[0]);
}
else // This spawn ID was not found in the `spawn2` table
return 0;
mysql_free_result(result);
}
else { // Query error
LogFile->write(EQEMuLog::Error, "Error setting pathgrid '%s': '%s'", query, errbuf);
return 0;
}
if (grid_num == 0) // Our spawn doesn't have a grid assigned to it -- we need to create a new grid and assign it to the spawn
{
CreatedNewGrid = true;
if((grid_num = GetFreeGrid(zoneid)) == 0) // There are no grids for the current zone -- create Grid #1
grid_num = 1;
if(!RunQuery(query, MakeAnyLenString(&query,"insert into grid set id='%i',zoneid= %i, type='%i', type2='%i'",grid_num,zoneid,type1,type2), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error adding grid '%s': '%s'", query, errbuf);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
query = 0;
if(!RunQuery(query, MakeAnyLenString(&query,"update spawn2 set pathgrid='%i' where id='%i'",grid_num,spawn2id), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error updating spawn2 pathing '%s': '%s'", query, errbuf);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
}
else // NPC had a grid assigned to it
CreatedNewGrid = false;
// Find out what the next waypoint is for this grid
query = 0;
if(RunQuery(query, MakeAnyLenString(&query,"SELECT max(`number`) FROM grid_entries WHERE zoneid='%i' AND gridid='%i'",zoneid,grid_num),errbuf,&result))
{
safe_delete_array(query);
row = mysql_fetch_row(result);
if(row[0] != 0)
next_wp_num = atoi(row[0]) + 1;
else // No waypoints in this grid yet
next_wp_num = 1;
mysql_free_result(result);
}
else { // Query error
LogFile->write(EQEMuLog::Error, "Error getting next waypoint id '%s': '%s'", query, errbuf);
return 0;
}
query = 0;
if(!RunQuery(query, MakeAnyLenString(&query,"INSERT INTO grid_entries(gridid,zoneid,`number`,x,y,z,pause,heading) VALUES (%i,%i,%i,%f,%f,%f,%i,%f)",grid_num,zoneid,next_wp_num,xpos,ypos,zpos,pause,heading), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error adding grid entry '%s': '%s'", query, errbuf);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
if(CreatedNewGrid)
return grid_num;
return 0;
} /*** END ZoneDatabase::AddWPForSpawn() ***/
uint32 ZoneDatabase::GetFreeGrid(uint16 zoneid) {
char *query = 0;
char errbuf[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
if (RunQuery(query, MakeAnyLenString(&query,"SELECT max(id) from grid where zoneid = %i",zoneid),errbuf,&result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
uint32 tmp=0;
if (row[0])
tmp = atoi(row[0]);
mysql_free_result(result);
tmp++;
return tmp;
}
mysql_free_result(result);
}
else {
LogFile->write(EQEMuLog::Error, "Error in GetFreeGrid query '%s': %s", query, errbuf);
safe_delete_array(query);
}
return 0;
}
int ZoneDatabase::GetHighestWaypoint(uint32 zoneid, uint32 gridid) {
char *query = 0;
char errbuff[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
int res = 0;
if (RunQuery(query, MakeAnyLenString(&query,
"SELECT COALESCE(MAX(number), 0) FROM grid_entries WHERE zoneid = %i AND gridid = %i",
zoneid, gridid),errbuff,&result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
res = atoi( row[0] );
}
mysql_free_result(result);
} else {
LogFile->write(EQEMuLog::Error, "Error in GetHighestWaypoint query '%s': %s", query, errbuff);
safe_delete_array(query);
}
return(res);
}
void NPC::SaveGuardSpotCharm()
{
guard_x_saved = guard_x;
guard_y_saved = guard_y;
guard_z_saved = guard_z;
guard_heading_saved = guard_heading;
}
void NPC::RestoreGuardSpotCharm()
{
guard_x = guard_x_saved;
guard_y = guard_y_saved;
guard_z = guard_z_saved;
guard_heading = guard_heading_saved;
}