diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index aa7897e7f..53a71e109 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -400,6 +400,7 @@ 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| 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: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2020_01_24_grid_centerpoint_wp.sql b/utils/sql/git/required/2020_01_24_grid_centerpoint_wp.sql new file mode 100644 index 000000000..b4a118242 --- /dev/null +++ b/utils/sql/git/required/2020_01_24_grid_centerpoint_wp.sql @@ -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; \ No newline at end of file diff --git a/zone/common.h b/zone/common.h index e06fa134a..2d97527fc 100644 --- a/zone/common.h +++ b/zone/common.h @@ -647,6 +647,19 @@ enum { SKILLUP_FAILURE = 2 }; +enum { + GridCircular, + GridRandom10, + GridRandom, + GridPatrol, + GridOneWayRepop, + GridRand5LoS, + GridOneWayDepop, + GridCenterPoint, + GridRandomCenterPoint, + GridRandomPath +}; + typedef enum { petFamiliar, //only listens to /pet get lost petAnimation, //does not listen to any commands diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 525948bb2..43d85c450 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1693,12 +1693,32 @@ void NPC::AI_DoMovement() { GetZ(), 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(); - SetAppearance(eaStanding, false); - if (cur_wp_pause > 0) { + if (GetAppearance() != eaStanding) { + SetAppearance(eaStanding, false); + } + if (cur_wp_pause > 0 && m_CurrentWayPoint.w >= 0.0) { RotateTo(m_CurrentWayPoint.w); } - + //kick off event_waypoint arrive char temp[16]; sprintf(temp, "%d", cur_wp); @@ -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 if (found_spawn) 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 if (found_spawn) found_spawn->SetNPCPointerNull(); diff --git a/zone/npc.h b/zone/npc.h index 84ab8b4ef..d0ecdf1ca 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -303,7 +303,7 @@ public: int GetMaxWp() const { return max_wp; } void DisplayWaypointInfo(Client *to); void CalculateNewWaypoint(); - void AssignWaypoints(int32 grid); + void AssignWaypoints(int32 grid, int start_wp = 0); void SetWaypointPause(); void UpdateWaypoint(int wp_index); @@ -312,7 +312,8 @@ public: void ResumeWandering(); void PauseWandering(int pausetime); void MoveTo(const glm::vec4& position, bool saveguardspot); - void GetClosestWaypoint(std::list &wp_list, int count, const glm::vec3& location); + void GetClosestWaypoints(std::list &wp_list, int count, const glm::vec3& location); + int GetClosestWaypoint(const glm::vec3& location); uint32 GetEquippedItemFromTextureSlot(uint8 material_slot) const; // returns item id int32 GetEquipmentMaterial(uint8 material_slot) const; diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index e6d265813..958500550 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -233,6 +233,20 @@ bool Spawn2::Process() { } 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->mod_prespawn(this); @@ -275,7 +289,7 @@ bool Spawn2::Process() { z ); - LoadGrid(); + LoadGrid(starting_wp); } else { LogSpawns("Spawn2 [{}]: Group [{}] spawned [{}] ([{}]) at ([{}], [{}], [{}]). Grid loading delayed", @@ -302,7 +316,7 @@ void Spawn2::Disable() enabled = false; } -void Spawn2::LoadGrid() { +void Spawn2::LoadGrid(int start_wp) { if (!npcthis) return; if (grid_ < 1) @@ -311,8 +325,8 @@ void Spawn2::LoadGrid() { return; //dont set an NPC's grid until its loaded for them. npcthis->SetGrid(grid_); - npcthis->AssignWaypoints(grid_); - LogSpawns("Spawn2 [{}]: Loading grid [{}] for [{}]", spawn2_id, grid_, npcthis->GetName()); + npcthis->AssignWaypoints(grid_, start_wp); + LogSpawns("Spawn2 [{}]: Loading grid [{}] for [{}]; starting wp is [{}]", spawn2_id, grid_, npcthis->GetName(), start_wp); } /* diff --git a/zone/spawn2.h b/zone/spawn2.h index a626b7084..bf6530876 100644 --- a/zone/spawn2.h +++ b/zone/spawn2.h @@ -36,7 +36,7 @@ public: uint16 cond_id = SC_AlwaysEnabled, int16 min_value = 0, bool in_enabled = true, EmuAppearance anim = eaStanding); ~Spawn2(); - void LoadGrid(); + void LoadGrid(int start_wp = 0); void Enable() { enabled = true; } void Disable(); bool Enabled() { return enabled; } diff --git a/zone/spawngroup.cpp b/zone/spawngroup.cpp index cc3735386..6a7817bce 100644 --- a/zone/spawngroup.cpp +++ b/zone/spawngroup.cpp @@ -48,7 +48,8 @@ SpawnGroup::SpawnGroup( int delay_in, int despawn_in, uint32 despawn_timer_in, - int min_delay_in + int min_delay_in, + bool wp_spawns_in ) { id = in_id; @@ -63,6 +64,7 @@ SpawnGroup::SpawnGroup( delay = delay_in; despawn = despawn_in; despawn_timer = despawn_timer_in; + wp_spawns = wp_spawns_in; } uint32 SpawnGroup::GetNPCType(uint16 in_filter) @@ -198,7 +200,8 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG spawngroup.delay, spawngroup.despawn, spawngroup.despawn_timer, - spawngroup.mindelay + spawngroup.mindelay, + spawngroup.wp_spawns FROM spawn2, spawngroup @@ -229,7 +232,8 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG atoi(row[8]), atoi(row[9]), atoi(row[10]), - atoi(row[11]) + atoi(row[11]), + atoi(row[12]) ); spawn_group_list->AddSpawnGroup(new_spawn_group); @@ -305,7 +309,8 @@ bool ZoneDatabase::LoadSpawnGroupsByID(int spawn_group_id, SpawnGroupList *spawn spawngroup.delay, spawngroup.despawn, spawngroup.despawn_timer, - spawngroup.mindelay + spawngroup.mindelay, + spawngroup.wp_spawns FROM spawngroup WHERE @@ -332,7 +337,8 @@ bool ZoneDatabase::LoadSpawnGroupsByID(int spawn_group_id, SpawnGroupList *spawn atoi(row[8]), atoi(row[9]), atoi(row[10]), - atoi(row[11]) + atoi(row[11]), + atoi(row[12]) ); spawn_group_list->AddSpawnGroup(new_spawn_group); diff --git a/zone/spawngroup.h b/zone/spawngroup.h index 2a4c1af6c..a1068cea0 100644 --- a/zone/spawngroup.h +++ b/zone/spawngroup.h @@ -49,13 +49,15 @@ public: int delay_in, int despawn_in, uint32 despawn_timer_in, - int min_delay_in + int min_delay_in, + bool wp_spawns_in ); ~SpawnGroup(); uint32 GetNPCType(uint16 condition_value_filter=1); void AddSpawnEntry(SpawnEntry *newEntry); 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 roambox[4]; int min_delay; diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index b6033744c..0bd18edbf 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -244,14 +244,14 @@ void NPC::CalculateNewWaypoint() int old_wp = cur_wp; bool reached_end = 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; if (cur_wp == 0) reached_beginning = true; switch (wandertype) { - case 0: //circle + case GridCircular: { if (reached_end) cur_wp = 0; @@ -259,10 +259,10 @@ void NPC::CalculateNewWaypoint() cur_wp = cur_wp + 1; break; } - case 1: //10 closest + case GridRandom10: { std::list closest; - GetClosestWaypoint(closest, 10, glm::vec3(GetPosition())); + GetClosestWaypoints(closest, 10, glm::vec3(GetPosition())); auto iter = closest.begin(); if (closest.size() != 0) { @@ -273,30 +273,64 @@ void NPC::CalculateNewWaypoint() break; } - case 2: //random + case GridRandom: + case GridCenterPoint: { - cur_wp = zone->random.Int(0, Waypoints.size() - 1); - if (cur_wp == old_wp) + if (wandertype == GridCenterPoint && !reached_beginning) { - if (cur_wp == (Waypoints.size() - 1)) + cur_wp = 0; + } + else + { + cur_wp = zone->random.Int(0, Waypoints.size() - 1); + if (cur_wp == old_wp || (wandertype == GridCenterPoint && cur_wp == 0)) { - if (cur_wp > 0) + if (cur_wp == (Waypoints.size() - 1)) { - cur_wp--; + if (cur_wp > 0) + { + cur_wp--; + } } - } - else if (cur_wp == 0) - { - if ((Waypoints.size() - 1) > 0) + else if (cur_wp == 0) { - cur_wp++; + if ((Waypoints.size() - 1) > 0) + { + cur_wp++; + } } } } break; } - case 3: //patrol + case GridRandomCenterPoint: + { + bool on_center = Waypoints[cur_wp].centerpoint; + std::vector 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) patrol = 1; @@ -309,16 +343,16 @@ void NPC::CalculateNewWaypoint() break; } - case 4: //goto the end and depop with spawn timer - case 6: //goto the end and depop without spawn timer + case GridOneWayRepop: + case GridOneWayDepop: { cur_wp = cur_wp + 1; break; } - case 5: //pick random closest 5 and pick one that's in sight + case GridRand5LoS: { std::list closest; - GetClosestWaypoint(closest, 5, glm::vec3(GetPosition())); + GetClosestWaypoints(closest, 5, glm::vec3(GetPosition())); auto iter = closest.begin(); while (iter != closest.end()) @@ -341,6 +375,25 @@ void NPC::CalculateNewWaypoint() } 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 @@ -357,7 +410,30 @@ bool wp_distance_pred(const wp_distance& left, const wp_distance& right) return left.dist < right.dist; } -void NPC::GetClosestWaypoint(std::list &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 &wp_list, int count, const glm::vec3& location) { wp_list.clear(); if (Waypoints.size() <= count) @@ -485,7 +561,7 @@ void Mob::StopNavigation() { mMovementManager->StopNavigation(this); } -void NPC::AssignWaypoints(int32 grid) +void NPC::AssignWaypoints(int32 grid, int start_wp) { if (grid == 0) return; // grid ID 0 not supported @@ -518,7 +594,7 @@ void NPC::AssignWaypoints(int32 grid) SetGrid(grid); // Assign grid number // 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 " "ORDER BY `number`", grid, zone->GetZoneID()); results = database.QueryDatabase(query); @@ -539,14 +615,22 @@ void NPC::AssignWaypoints(int32 grid) newwp.pause = atoi(row[3]); newwp.heading = atof(row[4]); + newwp.centerpoint = atobool(row[5]); Waypoints.push_back(newwp); } - UpdateWaypoint(0); + cur_wp = start_wp; + UpdateWaypoint(start_wp); 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(); + } 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]); } +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() { m_GuardPointSaved = m_GuardPoint; diff --git a/zone/zonedb.h b/zone/zonedb.h index f28ac5ec4..d5c02c693 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -47,6 +47,7 @@ struct wplist { float z; int pause; float heading; + bool centerpoint; }; #pragma pack(1) @@ -434,6 +435,7 @@ public: void AssignGrid(Client *client, int grid, int spawn2id); int GetHighestGrid(uint32 zoneid); int GetHighestWaypoint(uint32 zoneid, uint32 gridid); + int GetRandomWaypointLocFromGrid(glm::vec4 &loc, uint16 zoneid, int grid); /* NPCs */