Added new pathgrid type 7 (GridCenterPoint). This grid causes a NPC to alternate between the first waypoint in their grid (Number 1 in the editor) and a random waypoint. (1 - 7 - 1 - 4 - 1 - 11 - 1 - 5 - 1, etc)

Changed the wandertype IDs to an enum so we know what we're looking at.
Added new pathgrid type 8 (GridRandomCenterPoint).  (SQL required) This new type causes a NPC to alternate between a random waypoint in grid_entries and a random waypoint marked with the new centerpoint column set to true. If no waypoints are marked as a centerpoint, this wandertype will not work. There is no numbering requirement or limit for centerpoints. You can have as many as you need.
New spawngroup field: wp_spawns (SQL required). Added a new spawngroup field, which is a boolean that if true changes the behavior of spawngroups this way: If the spawnpoint in the spawngroup has a grid, the NPC will spawn at a random waypoint location taken from its grid instead of the spawnpoint location.
New randompath behavior: The randompath grid type will now use the closest waypoint as its current waypoint on spawning.  This allows multiple spawn locations to use the same grid without having the undesirable behavior of walking to the first waypoint through walls and ignoring waypoint nodes. NPC::GetClosestWaypoint() was renamed to NPC::GetClosestWaypoints() as it was filling a list of multiple waypoints. a new method NPC::GetClosestWaypoint() returns a single waypoint in the form of an integer.
This commit is contained in:
regneq 2020-01-24 15:11:08 -08:00
parent 453bee511a
commit c2b3e85272
11 changed files with 219 additions and 43 deletions

View File

@ -400,6 +400,7 @@
9144|2019_11_09_logsys_description_update.sql|SELECT * FROM db_version WHERE version >= 9143|empty| 9144|2019_11_09_logsys_description_update.sql|SELECT * FROM db_version WHERE version >= 9143|empty|
9145|2019_12_24_banned_ips_update.sql|SHOW TABLES LIKE 'Banned_IPs'|not_empty| 9145|2019_12_24_banned_ips_update.sql|SHOW TABLES LIKE 'Banned_IPs'|not_empty|
9146|2020_01_10_character_soft_deletes.sql|SHOW COLUMNS FROM `character_data` LIKE 'deleted_at'|empty| 9146|2020_01_10_character_soft_deletes.sql|SHOW COLUMNS FROM `character_data` LIKE 'deleted_at'|empty|
9147|2020_01_24_grid_centerpoint_wp.sql|SHOW COLUMNS FROM `grid_entries` LIKE 'centerpoint'|empty|
# Upgrade conditions: # Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not # This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1,2 @@
alter table grid_entries add column `centerpoint` tinyint(4) not null default 0;
alter table spawngroup add column `wp_spawns` tinyint(1) unsigned not null default 0;

View File

@ -647,6 +647,19 @@ enum {
SKILLUP_FAILURE = 2 SKILLUP_FAILURE = 2
}; };
enum {
GridCircular,
GridRandom10,
GridRandom,
GridPatrol,
GridOneWayRepop,
GridRand5LoS,
GridOneWayDepop,
GridCenterPoint,
GridRandomCenterPoint,
GridRandomPath
};
typedef enum { typedef enum {
petFamiliar, //only listens to /pet get lost petFamiliar, //only listens to /pet get lost
petAnimation, //does not listen to any commands petAnimation, //does not listen to any commands

View File

@ -1693,9 +1693,29 @@ void NPC::AI_DoMovement() {
GetZ(), GetZ(),
GetGrid()); GetGrid());
if (wandertype == GridRandomPath)
{
if (cur_wp == patrol)
{
// reached our randomly selected destination; force a pause
if (cur_wp_pause == 0)
{
if (Waypoints.size() > 0 && Waypoints[0].pause)
cur_wp_pause = Waypoints[0].pause;
else
cur_wp_pause = 38;
}
Log(Logs::Detail, Logs::AI, "NPC using wander type GridRandomPath on grid %d at waypoint %d has reached its random destination; pause time is %d", GetGrid(), cur_wp, cur_wp_pause);
}
else
cur_wp_pause = 0; // skipping pauses until destination
}
SetWaypointPause(); SetWaypointPause();
if (GetAppearance() != eaStanding) {
SetAppearance(eaStanding, false); SetAppearance(eaStanding, false);
if (cur_wp_pause > 0) { }
if (cur_wp_pause > 0 && m_CurrentWayPoint.w >= 0.0) {
RotateTo(m_CurrentWayPoint.w); RotateTo(m_CurrentWayPoint.w);
} }
@ -1789,12 +1809,12 @@ void NPC::AI_SetupNextWaypoint() {
} }
} }
if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) { if (wandertype == GridOneWayRepop && cur_wp == CastToNPC()->GetMaxWp()) {
CastToNPC()->Depop(true); //depop and restart spawn timer CastToNPC()->Depop(true); //depop and restart spawn timer
if (found_spawn) if (found_spawn)
found_spawn->SetNPCPointerNull(); found_spawn->SetNPCPointerNull();
} }
else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) { else if (wandertype == GridOneWayDepop && cur_wp == CastToNPC()->GetMaxWp()) {
CastToNPC()->Depop(false);//depop without spawn timer CastToNPC()->Depop(false);//depop without spawn timer
if (found_spawn) if (found_spawn)
found_spawn->SetNPCPointerNull(); found_spawn->SetNPCPointerNull();

View File

@ -303,7 +303,7 @@ public:
int GetMaxWp() const { return max_wp; } int GetMaxWp() const { return max_wp; }
void DisplayWaypointInfo(Client *to); void DisplayWaypointInfo(Client *to);
void CalculateNewWaypoint(); void CalculateNewWaypoint();
void AssignWaypoints(int32 grid); void AssignWaypoints(int32 grid, int start_wp = 0);
void SetWaypointPause(); void SetWaypointPause();
void UpdateWaypoint(int wp_index); void UpdateWaypoint(int wp_index);
@ -312,7 +312,8 @@ public:
void ResumeWandering(); void ResumeWandering();
void PauseWandering(int pausetime); void PauseWandering(int pausetime);
void MoveTo(const glm::vec4& position, bool saveguardspot); void MoveTo(const glm::vec4& position, bool saveguardspot);
void GetClosestWaypoint(std::list<wplist> &wp_list, int count, const glm::vec3& location); void GetClosestWaypoints(std::list<wplist> &wp_list, int count, const glm::vec3& location);
int GetClosestWaypoint(const glm::vec3& location);
uint32 GetEquippedItemFromTextureSlot(uint8 material_slot) const; // returns item id uint32 GetEquippedItemFromTextureSlot(uint8 material_slot) const; // returns item id
int32 GetEquipmentMaterial(uint8 material_slot) const; int32 GetEquipmentMaterial(uint8 material_slot) const;

View File

@ -233,6 +233,20 @@ bool Spawn2::Process() {
} }
currentnpcid = npcid; currentnpcid = npcid;
glm::vec4 loc(x, y, z, heading);
int starting_wp = 0;
if (spawn_group->wp_spawns && grid_ > 0)
{
glm::vec4 wploc;
starting_wp = database.GetRandomWaypointLocFromGrid(wploc, zone->GetZoneID(), grid_);
if (wploc.x != 0.0f || wploc.y != 0.0f || wploc.z != 0.0f)
{
loc = wploc;
Log(Logs::General, Logs::Spawns, "spawning at random waypoint #%i loc: (%.3f, %.3f, %.3f).", starting_wp , loc.x, loc.y, loc.z);
}
}
NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water); NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water);
npc->mod_prespawn(this); npc->mod_prespawn(this);
@ -275,7 +289,7 @@ bool Spawn2::Process() {
z z
); );
LoadGrid(); LoadGrid(starting_wp);
} }
else { else {
LogSpawns("Spawn2 [{}]: Group [{}] spawned [{}] ([{}]) at ([{}], [{}], [{}]). Grid loading delayed", LogSpawns("Spawn2 [{}]: Group [{}] spawned [{}] ([{}]) at ([{}], [{}], [{}]). Grid loading delayed",
@ -302,7 +316,7 @@ void Spawn2::Disable()
enabled = false; enabled = false;
} }
void Spawn2::LoadGrid() { void Spawn2::LoadGrid(int start_wp) {
if (!npcthis) if (!npcthis)
return; return;
if (grid_ < 1) if (grid_ < 1)
@ -311,8 +325,8 @@ void Spawn2::LoadGrid() {
return; return;
//dont set an NPC's grid until its loaded for them. //dont set an NPC's grid until its loaded for them.
npcthis->SetGrid(grid_); npcthis->SetGrid(grid_);
npcthis->AssignWaypoints(grid_); npcthis->AssignWaypoints(grid_, start_wp);
LogSpawns("Spawn2 [{}]: Loading grid [{}] for [{}]", spawn2_id, grid_, npcthis->GetName()); LogSpawns("Spawn2 [{}]: Loading grid [{}] for [{}]; starting wp is [{}]", spawn2_id, grid_, npcthis->GetName(), start_wp);
} }
/* /*

View File

@ -36,7 +36,7 @@ public:
uint16 cond_id = SC_AlwaysEnabled, int16 min_value = 0, bool in_enabled = true, EmuAppearance anim = eaStanding); uint16 cond_id = SC_AlwaysEnabled, int16 min_value = 0, bool in_enabled = true, EmuAppearance anim = eaStanding);
~Spawn2(); ~Spawn2();
void LoadGrid(); void LoadGrid(int start_wp = 0);
void Enable() { enabled = true; } void Enable() { enabled = true; }
void Disable(); void Disable();
bool Enabled() { return enabled; } bool Enabled() { return enabled; }

View File

@ -48,7 +48,8 @@ SpawnGroup::SpawnGroup(
int delay_in, int delay_in,
int despawn_in, int despawn_in,
uint32 despawn_timer_in, uint32 despawn_timer_in,
int min_delay_in int min_delay_in,
bool wp_spawns_in
) )
{ {
id = in_id; id = in_id;
@ -63,6 +64,7 @@ SpawnGroup::SpawnGroup(
delay = delay_in; delay = delay_in;
despawn = despawn_in; despawn = despawn_in;
despawn_timer = despawn_timer_in; despawn_timer = despawn_timer_in;
wp_spawns = wp_spawns_in;
} }
uint32 SpawnGroup::GetNPCType(uint16 in_filter) uint32 SpawnGroup::GetNPCType(uint16 in_filter)
@ -198,7 +200,8 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG
spawngroup.delay, spawngroup.delay,
spawngroup.despawn, spawngroup.despawn,
spawngroup.despawn_timer, spawngroup.despawn_timer,
spawngroup.mindelay spawngroup.mindelay,
spawngroup.wp_spawns
FROM FROM
spawn2, spawn2,
spawngroup spawngroup
@ -229,7 +232,8 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG
atoi(row[8]), atoi(row[8]),
atoi(row[9]), atoi(row[9]),
atoi(row[10]), atoi(row[10]),
atoi(row[11]) atoi(row[11]),
atoi(row[12])
); );
spawn_group_list->AddSpawnGroup(new_spawn_group); spawn_group_list->AddSpawnGroup(new_spawn_group);
@ -305,7 +309,8 @@ bool ZoneDatabase::LoadSpawnGroupsByID(int spawn_group_id, SpawnGroupList *spawn
spawngroup.delay, spawngroup.delay,
spawngroup.despawn, spawngroup.despawn,
spawngroup.despawn_timer, spawngroup.despawn_timer,
spawngroup.mindelay spawngroup.mindelay,
spawngroup.wp_spawns
FROM FROM
spawngroup spawngroup
WHERE WHERE
@ -332,7 +337,8 @@ bool ZoneDatabase::LoadSpawnGroupsByID(int spawn_group_id, SpawnGroupList *spawn
atoi(row[8]), atoi(row[8]),
atoi(row[9]), atoi(row[9]),
atoi(row[10]), atoi(row[10]),
atoi(row[11]) atoi(row[11]),
atoi(row[12])
); );
spawn_group_list->AddSpawnGroup(new_spawn_group); spawn_group_list->AddSpawnGroup(new_spawn_group);

View File

@ -49,13 +49,15 @@ public:
int delay_in, int delay_in,
int despawn_in, int despawn_in,
uint32 despawn_timer_in, uint32 despawn_timer_in,
int min_delay_in int min_delay_in,
bool wp_spawns_in
); );
~SpawnGroup(); ~SpawnGroup();
uint32 GetNPCType(uint16 condition_value_filter=1); uint32 GetNPCType(uint16 condition_value_filter=1);
void AddSpawnEntry(SpawnEntry *newEntry); void AddSpawnEntry(SpawnEntry *newEntry);
uint32 id; uint32 id;
bool wp_spawns; // if true, spawn NPCs at a random waypoint location (if spawnpoint has a grid) instead of the spawnpoint's loc
float roamdist; float roamdist;
float roambox[4]; float roambox[4];
int min_delay; int min_delay;

View File

@ -244,14 +244,14 @@ void NPC::CalculateNewWaypoint()
int old_wp = cur_wp; int old_wp = cur_wp;
bool reached_end = false; bool reached_end = false;
bool reached_beginning = false; bool reached_beginning = false;
if (cur_wp == max_wp) if (cur_wp == max_wp - 1) //cur_wp starts at 0, max_wp starts at 1.
reached_end = true; reached_end = true;
if (cur_wp == 0) if (cur_wp == 0)
reached_beginning = true; reached_beginning = true;
switch (wandertype) switch (wandertype)
{ {
case 0: //circle case GridCircular:
{ {
if (reached_end) if (reached_end)
cur_wp = 0; cur_wp = 0;
@ -259,10 +259,10 @@ void NPC::CalculateNewWaypoint()
cur_wp = cur_wp + 1; cur_wp = cur_wp + 1;
break; break;
} }
case 1: //10 closest case GridRandom10:
{ {
std::list<wplist> closest; std::list<wplist> closest;
GetClosestWaypoint(closest, 10, glm::vec3(GetPosition())); GetClosestWaypoints(closest, 10, glm::vec3(GetPosition()));
auto iter = closest.begin(); auto iter = closest.begin();
if (closest.size() != 0) if (closest.size() != 0)
{ {
@ -273,10 +273,17 @@ void NPC::CalculateNewWaypoint()
break; break;
} }
case 2: //random case GridRandom:
case GridCenterPoint:
{
if (wandertype == GridCenterPoint && !reached_beginning)
{
cur_wp = 0;
}
else
{ {
cur_wp = zone->random.Int(0, Waypoints.size() - 1); cur_wp = zone->random.Int(0, Waypoints.size() - 1);
if (cur_wp == old_wp) if (cur_wp == old_wp || (wandertype == GridCenterPoint && cur_wp == 0))
{ {
if (cur_wp == (Waypoints.size() - 1)) if (cur_wp == (Waypoints.size() - 1))
{ {
@ -293,10 +300,37 @@ void NPC::CalculateNewWaypoint()
} }
} }
} }
}
break; break;
} }
case 3: //patrol case GridRandomCenterPoint:
{
bool on_center = Waypoints[cur_wp].centerpoint;
std::vector<wplist> random_waypoints;
for (auto &w : Waypoints)
{
wplist wpl = w;
if (wpl.index != cur_wp &&
((on_center && !wpl.centerpoint) || (!on_center && wpl.centerpoint)))
{
random_waypoints.push_back(w);
}
}
if (random_waypoints.size() == 0)
{
cur_wp = 0;
}
else
{
int windex = zone->random.Roll0(random_waypoints.size());
cur_wp = random_waypoints[windex].index;
}
break;
}
case GridPatrol:
{ {
if (reached_end) if (reached_end)
patrol = 1; patrol = 1;
@ -309,16 +343,16 @@ void NPC::CalculateNewWaypoint()
break; break;
} }
case 4: //goto the end and depop with spawn timer case GridOneWayRepop:
case 6: //goto the end and depop without spawn timer case GridOneWayDepop:
{ {
cur_wp = cur_wp + 1; cur_wp = cur_wp + 1;
break; break;
} }
case 5: //pick random closest 5 and pick one that's in sight case GridRand5LoS:
{ {
std::list<wplist> closest; std::list<wplist> closest;
GetClosestWaypoint(closest, 5, glm::vec3(GetPosition())); GetClosestWaypoints(closest, 5, glm::vec3(GetPosition()));
auto iter = closest.begin(); auto iter = closest.begin();
while (iter != closest.end()) while (iter != closest.end())
@ -341,6 +375,25 @@ void NPC::CalculateNewWaypoint()
} }
break; break;
} }
case GridRandomPath: // randomly select a waypoint but follow path to it instead of walk directly to it ignoring walls
{
if (Waypoints.size() == 0)
{
cur_wp = 0;
}
else
{
if (cur_wp == patrol) // reutilizing patrol member instead of making new member for this wander type; here we use it to save a random waypoint
{
while (patrol == cur_wp)
patrol = zone->random.Int(0, Waypoints.size() - 1);
}
if (patrol > cur_wp)
cur_wp = cur_wp + 1;
else
cur_wp = cur_wp - 1;
}
}
} }
// Preserve waypoint setting for quest controlled NPCs // Preserve waypoint setting for quest controlled NPCs
@ -357,7 +410,30 @@ bool wp_distance_pred(const wp_distance& left, const wp_distance& right)
return left.dist < right.dist; return left.dist < right.dist;
} }
void NPC::GetClosestWaypoint(std::list<wplist> &wp_list, int count, const glm::vec3& location) int NPC::GetClosestWaypoint(const glm::vec3& location)
{
if (Waypoints.size() <= 1)
return 0;
int closest = 0;
float closestDist = 9999999.0f;
float dist;
for (int i = 0; i < Waypoints.size(); ++i)
{
dist = DistanceSquared(location, glm::vec3(Waypoints[i].x, Waypoints[i].y, Waypoints[i].z));
if (dist < closestDist)
{
closestDist = dist;
closest = i;
}
}
return closest;
}
// fills wp_list with the closest count number of waypoints
void NPC::GetClosestWaypoints(std::list<wplist> &wp_list, int count, const glm::vec3& location)
{ {
wp_list.clear(); wp_list.clear();
if (Waypoints.size() <= count) if (Waypoints.size() <= count)
@ -485,7 +561,7 @@ void Mob::StopNavigation() {
mMovementManager->StopNavigation(this); mMovementManager->StopNavigation(this);
} }
void NPC::AssignWaypoints(int32 grid) void NPC::AssignWaypoints(int32 grid, int start_wp)
{ {
if (grid == 0) if (grid == 0)
return; // grid ID 0 not supported return; // grid ID 0 not supported
@ -518,7 +594,7 @@ void NPC::AssignWaypoints(int32 grid)
SetGrid(grid); // Assign grid number SetGrid(grid); // Assign grid number
// Retrieve all waypoints for this grid // Retrieve all waypoints for this grid
query = StringFormat("SELECT `x`,`y`,`z`,`pause`,`heading` " query = StringFormat("SELECT `x`,`y`,`z`,`pause`,`heading`, `centerpoint` "
"FROM grid_entries WHERE `gridid` = %i AND `zoneid` = %i " "FROM grid_entries WHERE `gridid` = %i AND `zoneid` = %i "
"ORDER BY `number`", grid, zone->GetZoneID()); "ORDER BY `number`", grid, zone->GetZoneID());
results = database.QueryDatabase(query); results = database.QueryDatabase(query);
@ -539,14 +615,22 @@ void NPC::AssignWaypoints(int32 grid)
newwp.pause = atoi(row[3]); newwp.pause = atoi(row[3]);
newwp.heading = atof(row[4]); newwp.heading = atof(row[4]);
newwp.centerpoint = atobool(row[5]);
Waypoints.push_back(newwp); Waypoints.push_back(newwp);
} }
UpdateWaypoint(0); cur_wp = start_wp;
UpdateWaypoint(start_wp);
SetWaypointPause(); SetWaypointPause();
if (wandertype == 1 || wandertype == 2 || wandertype == 5) if (wandertype == GridRandomPath) {
cur_wp = GetClosestWaypoint(glm::vec3(GetPosition()));
patrol = cur_wp;
}
if (wandertype == GridRandom10 || wandertype == GridRandom || wandertype == GridRand5LoS)
CalculateNewWaypoint(); CalculateNewWaypoint();
} }
void Mob::SendTo(float new_x, float new_y, float new_z) { void Mob::SendTo(float new_x, float new_y, float new_z) {
@ -1058,6 +1142,37 @@ int ZoneDatabase::GetHighestWaypoint(uint32 zoneid, uint32 gridid) {
return atoi(row[0]); return atoi(row[0]);
} }
int ZoneDatabase::GetRandomWaypointLocFromGrid(glm::vec4 &loc, uint16 zoneid, int grid)
{
loc.x = loc.y = loc.z = loc.w = 0.0f;
std::string query = StringFormat("SELECT `x`,`y`,`z`,`heading` "
"FROM grid_entries WHERE `gridid` = %i AND `zoneid` = %u ORDER BY `number`", grid, zone->GetZoneID());
auto results = database.QueryDatabase(query);
if (!results.Success()) {
Log(Logs::General, Logs::Error, "MySQL Error while trying get random waypoint loc from grid %i in zoneid %u; %s", grid, zoneid, results.ErrorMessage().c_str());
return 0;
}
if (results.RowCount() > 0)
{
int roll = zone->random.Int(0, results.RowCount() - 1);
int i = 0;
auto row = results.begin();
while (i < roll)
{
row++;
i++;
}
loc.x = atof(row[0]);
loc.y = atof(row[1]);
loc.z = atof(row[2]);
loc.w = atof(row[3]);
return i;
}
return 0;
}
void NPC::SaveGuardSpotCharm() void NPC::SaveGuardSpotCharm()
{ {
m_GuardPointSaved = m_GuardPoint; m_GuardPointSaved = m_GuardPoint;

View File

@ -47,6 +47,7 @@ struct wplist {
float z; float z;
int pause; int pause;
float heading; float heading;
bool centerpoint;
}; };
#pragma pack(1) #pragma pack(1)
@ -434,6 +435,7 @@ public:
void AssignGrid(Client *client, int grid, int spawn2id); void AssignGrid(Client *client, int grid, int spawn2id);
int GetHighestGrid(uint32 zoneid); int GetHighestGrid(uint32 zoneid);
int GetHighestWaypoint(uint32 zoneid, uint32 gridid); int GetHighestWaypoint(uint32 zoneid, uint32 gridid);
int GetRandomWaypointLocFromGrid(glm::vec4 &loc, uint16 zoneid, int grid);
/* NPCs */ /* NPCs */