Skip to content

Commit

Permalink
[Zones] Convert IDLE_WHEN_EMPTY to a Zone Column (#3891)
Browse files Browse the repository at this point in the history
* [Rules] Convert IDLE_WHEN_EMPTY to a rule

# Notes
- Converts `IDLE_WHEN_EMPTY` to `Zone:ZonesIdleWhenEmpty` so that we can change this on the fly or on a zone-by-zone basis instead of having to recompile to do this.
- Especially helpful for those using release binaries that do not compile their own source.

* Convert to zone column.

* Update ruletypes.h

* Update ruletypes.h

* Update entity.cpp

* Update entity.cpp

* Rename.

* Update database_update_manifest.cpp

* Update base_zone_repository.h

* Update zone.cpp

* seconds_before_idle

* Update database_update_manifest.cpp

* Getter/Setters/Private

* Update base_zone_repository.h

* IsIdle()/SetIsIdle()

* Update entity.cpp
  • Loading branch information
Kinglykrab authored Jan 13, 2024
1 parent d41bd8f commit 742b437
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 179 deletions.
14 changes: 13 additions & 1 deletion common/database/database_update_manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5186,7 +5186,19 @@ ALTER TABLE `instance_list`
ADD COLUMN `notes` varchar(50) NOT NULL DEFAULT '' AFTER `never_expires`;
)",
},

ManifestEntry{
.version = 9252,
.description = "2024_01_07_zone_idle_when_empty.sql",
.check = "SHOW COLUMNS FROM `zone` LIKE 'idle_when_empty'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `zone`
ADD COLUMN `idle_when_empty` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 AFTER `min_lava_damage`
ADD COLUMN `seconds_before_idle` int(11) UNSIGNED NOT NULL DEFAULT 60 AFTER `idle_when_empty`
)",
.content_schema_update = true
}
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{
// .version = 9228,
Expand Down
5 changes: 0 additions & 5 deletions common/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ Core Zone features
*/


//Uncomment this to cause a zone to basically idle
//when there are no players in it, mobs stop wandering, etc..
#define IDLE_WHEN_EMPTY

#ifdef EMBPERL
//Enable the new XS based perl parser
#define EMBPERL_XS
Expand Down
252 changes: 138 additions & 114 deletions common/repositories/base/base_zone_repository.h

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion common/ruletypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ RULE_BOOL(Zone, UseZoneController, true, "Enables the ability to use persistent
RULE_BOOL(Zone, EnableZoneControllerGlobals, false, "Enables the ability to use quest globals with the zone controller NPC")
RULE_INT(Zone, GlobalLootMultiplier, 1, "Sets Global Loot drop multiplier for database based drops, useful for double, triple loot etc")
RULE_BOOL(Zone, KillProcessOnDynamicShutdown, true, "When process has booted a zone and has hit its zone shut down timer, it will hard kill the process to free memory back to the OS")
RULE_INT(Zone, SecondsBeforeIdle, 60, "Seconds before IDLE_WHEN_EMPTY define kicks in")
RULE_INT(Zone, SpawnEventMin, 3, "When strict is set in spawn_events, specifies the max EQ minutes into the trigger hour a spawn_event will fire. Going below 3 may cause the spawn_event to not fire.")
RULE_INT(Zone, ForageChance, 25, "Chance of foraging from zone table vs global table")
RULE_CATEGORY_END()
Expand Down
2 changes: 1 addition & 1 deletion common/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/

#define CURRENT_BINARY_DATABASE_VERSION 9251
#define CURRENT_BINARY_DATABASE_VERSION 9252

#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9041

Expand Down
34 changes: 34 additions & 0 deletions common/zone_store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1341,3 +1341,37 @@ int ZoneStore::GetZoneMinimumLavaDamage(uint32 zone_id, int version)

return 0;
}

uint8 ZoneStore::GetZoneIdleWhenEmpty(uint32 zone_id, int version)
{
for (auto &z: m_zones) {
if (z.zoneidnumber == zone_id && z.version == version) {
return z.idle_when_empty;
}
}

for (auto &z: m_zones) {
if (z.zoneidnumber == zone_id && z.version == 0) {
return z.idle_when_empty;
}
}

return 1;
}

uint32 ZoneStore::GetZoneSecondsBeforeIdle(uint32 zone_id, int version)
{
for (auto &z: m_zones) {
if (z.zoneidnumber == zone_id && z.version == version) {
return z.seconds_before_idle;
}
}

for (auto &z: m_zones) {
if (z.zoneidnumber == zone_id && z.version == 0) {
return z.seconds_before_idle;
}
}

return 60;
}
2 changes: 2 additions & 0 deletions common/zone_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ class ZoneStore {
int GetZoneUnderworldTeleportIndex(uint32 zone_id, int version = 0);
int GetZoneLavaDamage(uint32 zone_id, int version = 0);
int GetZoneMinimumLavaDamage(uint32 zone_id, int version = 0);
uint8 GetZoneIdleWhenEmpty(uint32 zone_id, int version = 0);
uint32 GetZoneSecondsBeforeIdle(uint32 zone_id, int version = 0);

private:
std::vector<ZoneRepository::Zone> m_zones;
Expand Down
24 changes: 24 additions & 0 deletions zone/embparser_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5703,6 +5703,26 @@ int Perl__GetZoneMinimumLavaDamage(uint32 zone_id, int version)
return zone_store.GetZoneMinimumLavaDamage(zone_id, version);
}

uint8 Perl__GetZoneIdleWhenEmpty(uint32 zone_id)
{
return zone_store.GetZoneIdleWhenEmpty(zone_id);
}

uint8 Perl__GetZoneIdleWhenEmpty(uint32 zone_id, int version)
{
return zone_store.GetZoneIdleWhenEmpty(zone_id, version);
}

uint32 Perl__GetZoneSecondsBeforeIdle(uint32 zone_id)
{
return zone_store.GetZoneSecondsBeforeIdle(zone_id);
}

uint32 Perl__GetZoneSecondsBeforeIdle(uint32 zone_id, int version)
{
return zone_store.GetZoneSecondsBeforeIdle(zone_id, version);
}

void Perl__send_channel_message(uint8 channel_number, uint32 guild_id, uint8 language_id, uint8 language_skill, const char* message)
{
quest_manager.SendChannelMessage(channel_number, guild_id, language_id, language_skill, message);
Expand Down Expand Up @@ -5810,6 +5830,8 @@ void perl_register_quest()
package.add("GetZoneGraveyardID", (float(*)(uint32, int))&Perl__GetZoneGraveyardID);
package.add("GetZoneHotzone", (uint8(*)(uint32))&Perl__GetZoneHotzone);
package.add("GetZoneHotzone", (uint8(*)(uint32, int))&Perl__GetZoneHotzone);
package.add("GetZoneIdleWhenEmpty", (uint8(*)(uint32))&Perl__GetZoneIdleWhenEmpty);
package.add("GetZoneIdleWhenEmpty", (uint8(*)(uint32, int))&Perl__GetZoneIdleWhenEmpty);
package.add("GetZoneInstanceType", (uint8(*)(uint32))&Perl__GetZoneInstanceType);
package.add("GetZoneInstanceType", (uint8(*)(uint32, int))&Perl__GetZoneInstanceType);
package.add("GetZoneID", &Perl__GetZoneID);
Expand Down Expand Up @@ -5890,6 +5912,8 @@ void perl_register_quest()
package.add("GetZoneSafeY", (float(*)(uint32, int))&Perl__GetZoneSafeY);
package.add("GetZoneSafeZ", (float(*)(uint32))&Perl__GetZoneSafeZ);
package.add("GetZoneSafeZ", (float(*)(uint32, int))&Perl__GetZoneSafeZ);
package.add("GetZoneSecondsBeforeIdle", (uint32(*)(uint32))&Perl__GetZoneSecondsBeforeIdle);
package.add("GetZoneSecondsBeforeIdle", (uint32(*)(uint32, int))&Perl__GetZoneSecondsBeforeIdle);
package.add("GetZoneShutdownDelay", (uint64(*)(uint32))&Perl__GetZoneShutdownDelay);
package.add("GetZoneShutdownDelay", (uint64(*)(uint32, int))&Perl__GetZoneShutdownDelay);
package.add("GetZoneSky", (uint8(*)(uint32))&Perl__GetZoneSky);
Expand Down
124 changes: 78 additions & 46 deletions zone/entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,10 +411,12 @@ void EntityList::RaidProcess()

void EntityList::DoorProcess()
{
#ifdef IDLE_WHEN_EMPTY
if (numclients < 1)
return;
#endif
if (zone && zone->IsIdleWhenEmpty()) {
if (numclients < 1) {
return;
}
}

if (door_list.empty()) {
door_timer.Disable();
return;
Expand Down Expand Up @@ -480,46 +482,76 @@ void EntityList::MobProcess()

size_t sz = mob_list.size();

#ifdef IDLE_WHEN_EMPTY
static int old_client_count=0;
static int old_client_count = 0;
static Timer *mob_settle_timer = new Timer();

if (numclients == 0 && old_client_count > 0 &&
RuleI(Zone, SecondsBeforeIdle) > 0) {
// Start Timer to allow any mobs that chased chars from zone
// to return home.
mob_settle_timer->Start(RuleI(Zone, SecondsBeforeIdle) * 1000);
}
if (zone->IsIdleWhenEmpty()) {
if (
numclients == 0 &&
old_client_count > 0 &&
zone->GetSecondsBeforeIdle() > 0
) {
if (!zone->IsIdle()) {
LogInfo(
"Zone will go into an idle state after [{}] second{}.",
zone->GetSecondsBeforeIdle(),
zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
);
}

old_client_count = numclients;
mob_settle_timer->Start(zone->GetSecondsBeforeIdle() * 1000);
}

// Disable settle timer if someone zones into empty zone
if (numclients > 0 || mob_settle_timer->Check()) {
mob_settle_timer->Disable();
}
old_client_count = numclients;

if (numclients == 0 && mob_settle_timer->Check()) {
if (!zone->IsIdle()) {
LogInfo(
"Zone has gone idle after [{}] second{}.",
zone->GetSecondsBeforeIdle(),
zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
);

zone->SetIsIdle(true);
}
}

Spawn2* s2 = mob->CastToNPC()->respawn2;
// Disable settle timer if someone zones into empty zone
if (numclients > 0 || mob_settle_timer->Check()) {
if (zone->IsIdle()) {
LogInfo("Zone is no longer idle.");

zone->SetIsIdle(false);
}

mob_settle_timer->Disable();
}

// Perform normal mob processing if any of these are true:
// -- zone is not empty
// -- a quest has turned it on for this zone while zone is idle
// -- the entity's spawn2 point is marked as path_while_zone_idle
// -- the zone is newly empty and we're allowing mobs to settle
if (zone->process_mobs_while_empty || numclients > 0 ||
(s2 && s2->PathWhenZoneIdle()) || mob_settle_timer->Enabled()) {
Spawn2* s2 = mob->CastToNPC()->respawn2;

// Perform normal mob processing if any of these are true:
// -- zone is not empty
// -- a quest has turned it on for this zone while zone is idle
// -- the entity's spawn2 point is marked as path_while_zone_idle
// -- the zone is newly empty and we're allowing mobs to settle
if (
numclients > 0 ||
(s2 && s2->PathWhenZoneIdle()) ||
mob_settle_timer->Enabled()
) {
mob_dead = !mob->Process();
} else {
// spawn_events can cause spawns and deaths while zone empty.
// At the very least, process that.
mob_dead = mob->CastToNPC()->GetDepop();
}
} else {
mob_dead = !mob->Process();
}
else {
// spawn_events can cause spawns and deaths while zone empty.
// At the very least, process that.
mob_dead = mob->CastToNPC()->GetDepop();
}
#else
mob_dead = !mob->Process();
#endif

size_t a_sz = mob_list.size();

if(a_sz > sz) {
if (a_sz > sz) {
//increased size can potentially screw with iterators so reset it to current value
//if buckets are re-orderered we may skip a process here and there but since
//process happens so often it shouldn't matter much
Expand All @@ -529,30 +561,30 @@ void EntityList::MobProcess()
++it;
}

if(mob_dead) {
if(mob->IsMerc()) {
if (mob_dead) {
if (mob->IsMerc()) {
entity_list.RemoveMerc(id);
}
else if(mob->IsBot()) {
} else if (mob->IsBot()) {
entity_list.RemoveBot(id);
}
else if(mob->IsNPC()) {
} else if (mob->IsNPC()) {
entity_list.RemoveNPC(id);
}
else {
} else {
#ifdef _WINDOWS
struct in_addr in;
in.s_addr = mob->CastToClient()->GetIP();
LogInfo("Dropping client: Process=false, ip=[{}] port=[{}]", inet_ntoa(in), mob->CastToClient()->GetPort());
#endif
Group *g = GetGroupByMob(mob);
if(g) {

Group* g = GetGroupByMob(mob);
if (g) {
g->DelMember(mob);
}
Raid *r = entity_list.GetRaidByClient(mob->CastToClient());
if(r) {

Raid* r = entity_list.GetRaidByClient(mob->CastToClient());
if (r) {
r->MemberZoned(mob->CastToClient());
}

entity_list.RemoveClient(id);
}

Expand Down
24 changes: 24 additions & 0 deletions zone/lua_general.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4743,6 +4743,26 @@ int lua_get_zone_minimum_lava_damage(uint32 zone_id, int version)
return zone_store.GetZoneMinimumLavaDamage(zone_id, version);
}

uint8 lua_get_zone_idle_when_empty(uint32 zone_id)
{
return zone_store.GetZoneIdleWhenEmpty(zone_id);
}

uint8 lua_get_zone_idle_when_empty(uint32 zone_id, int version)
{
return zone_store.GetZoneIdleWhenEmpty(zone_id, version);
}

uint32 lua_get_zone_seconds_before_idle(uint32 zone_id)
{
return zone_store.GetZoneSecondsBeforeIdle(zone_id);
}

uint32 lua_get_zone_seconds_before_idle(uint32 zone_id, int version)
{
return zone_store.GetZoneSecondsBeforeIdle(zone_id, version);
}

void lua_send_channel_message(uint8 channel_number, uint32 guild_id, uint8 language_id, uint8 language_skill, const char* message)
{
quest_manager.SendChannelMessage(channel_number, guild_id, language_id, language_skill, message);
Expand Down Expand Up @@ -6034,6 +6054,10 @@ luabind::scope lua_register_general() {
luabind::def("get_zone_lava_damage", (int(*)(uint32,int))&lua_get_zone_lava_damage),
luabind::def("get_zone_minimum_lava_damage", (int(*)(uint32))&lua_get_zone_minimum_lava_damage),
luabind::def("get_zone_minimum_lava_damage", (int(*)(uint32,int))&lua_get_zone_minimum_lava_damage),
luabind::def("get_zone_idle_when_empty", (uint8(*)(uint32))&lua_get_zone_idle_when_empty),
luabind::def("get_zone_idle_when_empty", (uint8(*)(uint32,int))&lua_get_zone_idle_when_empty),
luabind::def("get_zone_seconds_before_idle", (uint32(*)(uint32))&lua_get_zone_seconds_before_idle),
luabind::def("get_zone_seconds_before_idle", (uint32(*)(uint32,int))&lua_get_zone_seconds_before_idle),
luabind::def("send_channel_message", (void(*)(uint8,uint32,uint8,uint8,const char*))&lua_send_channel_message),
luabind::def("send_channel_message", (void(*)(Lua_Client,uint8,uint32,uint8,uint8,const char*))&lua_send_channel_message),
luabind::def("send_channel_message", (void(*)(Lua_Client,const char*,uint8,uint32,uint8,uint8,const char*))&lua_send_channel_message),
Expand Down
13 changes: 7 additions & 6 deletions zone/questmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -933,12 +933,13 @@ void QuestManager::repopzone() {
}
}

void QuestManager::processmobswhilezoneempty(bool on) {
if(zone) {
zone->process_mobs_while_empty = on;
}
else {
LogQuests("QuestManager::processmobswhilezoneempty called with nullptr zone. Probably syntax error in quest file");
void QuestManager::processmobswhilezoneempty(bool idle_when_empty) {
if (zone) {
zone->SetIdleWhenEmpty(idle_when_empty);
} else {
LogQuests(
"QuestManager::processmobswhilezoneempty called with nullptr zone. Probably syntax error in quest file"
);
}
}

Expand Down
2 changes: 1 addition & 1 deletion zone/questmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class QuestManager {
void depopall(int npc_type = 0);
void depopzone(bool StartSpawnTimer = true);
void repopzone();
void processmobswhilezoneempty(bool on);
void processmobswhilezoneempty(bool idle_when_empty);
void settarget(const char *type, int target_id);
void follow(int entity_id, int distance);
void sfollow();
Expand Down
Loading

0 comments on commit 742b437

Please sign in to comment.