[Zones] Add Max Level Check to Zones. (#2714)

* [Zones] Add Max Level Check to Zones.

# Perl
- Add `$client->CanEnterZone(zone_short_name)`.
- Add `$client->CanEnterZone(zone_short_name, instance_version)`.

# Lua
- Add `client:CanEnterZone(zone_short_name)`.
- Add `client:CanEnterZone(zone_short_name, instance_version)`.

# Notes
- Allows operators to limit zones to a maximum level.
- Allows operators to see if a player can enter a zone before sending them.
- Keeps players from entering via `#peqzone` and `#zone` as well if they do not meet the requirements or are not high enough status to bypass the requirements.

* Cleanup.
This commit is contained in:
Alex King
2023-01-10 20:47:37 -05:00
committed by GitHub
parent 4c8b65ecc6
commit 0d23ffe5e5
13 changed files with 458 additions and 420 deletions
+2 -1
View File
@@ -1352,6 +1352,8 @@ public:
void ClearPendingAdventureDoorClick() { safe_delete(adventure_door_timer); }
void ClearPendingAdventureData();
bool CanEnterZone(std::string zone_short_name = "", int16 instance_version = -1);
int GetAggroCount();
void IncrementAggroCount(bool raid_target = false);
void DecrementAggroCount();
@@ -1861,7 +1863,6 @@ private:
void NPCSpawn(const Seperator* sep);
uint32 GetEXPForLevel(uint16 level);
bool CanBeInZone();
void SendLogoutPackets();
void SendZoneInPackets();
bool AddPacket(const EQApplicationPacket *, bool);
+2 -2
View File
@@ -904,9 +904,9 @@ void Client::CompleteConnect()
heroforge_wearchange_timer.Start(250);
// enforce some rules..
if (!CanBeInZone()) {
if (!CanEnterZone()) {
LogInfo("Kicking character [{}] from zone, not allowed here (missing requirements)", GetCleanName());
GoToSafeCoords(ZoneID("arena"), 0);
GoToBind();
return;
}
}
-7
View File
@@ -53,13 +53,6 @@ void command_zone(Client *c, const Seperator *sep)
return;
}
// status checking
auto min_status = content_db.GetMinStatus(zone_id, 0);
if (c->Admin() < min_status) {
c->Message(Chat::White, "Your status is not high enough to go to this zone.");
return;
}
#ifdef BOTS
// This block is necessary to clean up any bot objects owned by a Client
if (zone_id != c->GetZoneID()) {
+12
View File
@@ -2926,6 +2926,16 @@ uint64 Lua_Client::CalcEXP(uint8 consider_level, bool ignore_modifiers) {
return self->CalcEXP(consider_level, ignore_modifiers);
}
bool Lua_Client::CanEnterZone(std::string zone_short_name) {
Lua_Safe_Call_Bool();
return self->CanEnterZone(zone_short_name);
}
bool Lua_Client::CanEnterZone(std::string zone_short_name, int16 instance_version) {
Lua_Safe_Call_Bool();
return self->CanEnterZone(zone_short_name, instance_version);
}
#ifdef BOTS
int Lua_Client::GetBotRequiredLevel()
@@ -3060,6 +3070,8 @@ luabind::scope lua_register_client() {
.def("CalcEXP", (uint64(Lua_Client::*)(uint8))&Lua_Client::CalcEXP)
.def("CalcEXP", (uint64(Lua_Client::*)(uint8,bool))&Lua_Client::CalcEXP)
.def("CalcPriceMod", (float(Lua_Client::*)(Lua_Mob,bool))&Lua_Client::CalcPriceMod)
.def("CanEnterZone", (bool(Lua_Client::*)(std::string))&Lua_Client::CanEnterZone)
.def("CanEnterZone", (bool(Lua_Client::*)(std::string,int16))&Lua_Client::CanEnterZone)
.def("CanHaveSkill", (bool(Lua_Client::*)(int))&Lua_Client::CanHaveSkill)
.def("CashReward", &Lua_Client::CashReward)
.def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName)
+2
View File
@@ -459,6 +459,8 @@ public:
void SetEXPEnabled(bool is_exp_enabled);
uint64 CalcEXP(uint8 consider_level);
uint64 CalcEXP(uint8 consider_level, bool ignore_modifiers);
bool CanEnterZone(std::string zone_short_name);
bool CanEnterZone(std::string zone_short_name, int16 instance_version);
void ApplySpell(int spell_id);
void ApplySpell(int spell_id, int duration);
+12
View File
@@ -2799,6 +2799,16 @@ void Perl_Client_SetEXPEnabled(Client* self, bool is_exp_enabled)
self->SetEXPEnabled(is_exp_enabled);
}
bool Perl_Client_CanEnterZone(Client* self, std::string zone_short_name)
{
return self->CanEnterZone(zone_short_name);
}
bool Perl_Client_CanEnterZone(Client* self, std::string zone_short_name, int16 instance_version)
{
return self->CanEnterZone(zone_short_name, instance_version);
}
#ifdef BOTS
int Perl_Client_GetBotRequiredLevel(Client* self)
@@ -2924,6 +2934,8 @@ void perl_register_client()
package.add("CalcPriceMod", (float(*)(Client*))&Perl_Client_CalcPriceMod);
package.add("CalcPriceMod", (float(*)(Client*, Mob*))&Perl_Client_CalcPriceMod);
package.add("CalcPriceMod", (float(*)(Client*, Mob*, bool))&Perl_Client_CalcPriceMod);
package.add("CanEnterZone", (bool(*)(Client*, std::string))&Perl_Client_CanEnterZone);
package.add("CanEnterZone", (bool(*)(Client*, std::string, int16))&Perl_Client_CanEnterZone);
package.add("CanHaveSkill", &Perl_Client_CanHaveSkill);
package.add("CashReward", &Perl_Client_CashReward);
package.add("ChangeLastName", &Perl_Client_ChangeLastName);
+1 -1
View File
@@ -362,7 +362,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
ZoneChange_Struct* zc2 = (ZoneChange_Struct*)outapp->pBuffer;
if (ztz->response <= 0) {
zc2->success = ZONE_ERROR_NOTREADY;
zc2->success = ZoningMessage::ZoneNotReady;
entity->CastToMob()->SetZone(ztz->current_zone_id, ztz->current_instance_id);
entity->CastToClient()->SetZoning(false);
entity->CastToClient()->SetLockSavePosition(false);
+51 -51
View File
@@ -297,30 +297,14 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
break;
};
//OK, now we should know where were going...
auto zoning_message = ZoningMessage::ZoneSuccess;
//Check some rules first.
int8 myerror = 1; //1 is succes
//not sure when we would use ZONE_ERROR_NOTREADY
//enforce min status and level
if (!ignore_restrictions && (Admin() < min_status || GetLevel() < zone_data->min_level)) {
myerror = ZONE_ERROR_NOEXPERIENCE;
}
if (!ignore_restrictions && !zone_data->flag_needed.empty()) {
//the flag needed string is not empty, meaning a flag is required.
if (Admin() < minStatusToIgnoreZoneFlags && !HasZoneFlag(target_zone_id)) {
LogInfo(
"Client [{}] does not have the proper flag to enter [{}] ({})",
GetCleanName(),
ZoneName(target_zone_id),
target_zone_id
);
Message(Chat::Red, "You do not have the flag to enter %s.", target_zone_name);
myerror = ZONE_ERROR_NOEXPERIENCE;
}
// Check Minimum Status, Minimum Level, Maximum Level, and Zone Flag
if (
!ignore_restrictions &&
!CanEnterZone(ZoneName(target_zone_id), target_instance_version)
) {
zoning_message = ZoningMessage::ZoneNoExperience;
}
//TODO: ADVENTURE ENTRANCE CHECK
@@ -345,7 +329,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
);
if (!meets_zone_expansion_check) {
myerror = ZONE_ERROR_NOEXPANSION;
zoning_message = ZoningMessage::ZoneNoExpansion;
}
}
@@ -353,12 +337,11 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
LogInfo("[{}] Bypassing Expansion zone checks because GM status is set", GetCleanName());
}
if (myerror == 1) {
//we have successfully zoned
if (zoning_message == ZoningMessage::ZoneSuccess) {
DoZoneSuccess(zc, target_zone_id, target_instance_id, target_x, target_y, target_z, target_heading, ignore_restrictions);
} else {
LogError("Zoning [{}]: Rules prevent this char from zoning into [{}]", GetName(), target_zone_name);
SendZoneError(zc, myerror);
SendZoneError(zc, zoning_message);
}
}
@@ -1215,47 +1198,64 @@ void Client::SetPEQZoneFlag(uint32 zone_id) {
}
}
bool Client::CanBeInZone() {
bool Client::CanEnterZone(std::string zone_short_name, int16 instance_version) {
//check some critial rules to see if this char needs to be booted from the zone
//only enforce rules here which are serious enough to warrant being kicked from
//the zone
if (Admin() >= RuleI(GM, MinStatusToZoneAnywhere)) {
return (true);
return true;
}
float safe_x, safe_y, safe_z, safe_heading;
int16 min_status = AccountStatus::Player;
uint8 min_level = 0;
auto z = GetZoneVersionWithFallback(
ZoneID(zone->GetShortName()),
zone->GetInstanceVersion()
zone_short_name.empty() ? ZoneID(zone->GetShortName()) : ZoneID(zone_short_name),
instance_version == -1 ? zone->GetInstanceVersion() : instance_version
);
if (!z) {
return false;
}
safe_x = z->safe_x;
safe_y = z->safe_y;
safe_z = z->safe_z;
safe_heading = z->safe_heading;
min_status = z->min_status;
min_level = z->min_level;
if (GetLevel() < min_level) {
LogDebug("[CLIENT] Character does not meet min level requirement ([{}] < [{}])!", GetLevel(), min_level);
return (false);
if (GetLevel() < z->min_level) {
LogInfo(
"Character [{}] does not meet minimum level requirement ([{}] < [{}])!",
GetCleanName(),
GetLevel(),
z->min_level
);
return false;
}
if (Admin() < min_status) {
LogDebug("[CLIENT] Character does not meet min status requirement ([{}] < [{}])!", Admin(), min_status);
return (false);
if (GetLevel() > z->max_level) {
LogInfo(
"Character [{}] does not meet maximum level requirement ([{}] > [{}])!",
GetCleanName(),
GetLevel(),
z->max_level
);
return false;
}
if (Admin() < z->min_status) {
LogInfo(
"Character [{}] does not meet minimum status requirement ([{}] < [{}])!",
GetCleanName(),
Admin(),
z->min_status
);
return false;
}
if (!z->flag_needed.empty()) {
//the flag needed string is not empty, meaning a flag is required.
if (Admin() < minStatusToIgnoreZoneFlags && !HasZoneFlag(zone->GetZoneID())) {
LogInfo("Character [{}] does not have the flag to be in this zone [{}]!", GetCleanName(), z->flag_needed);
if (
Admin() < minStatusToIgnoreZoneFlags &&
!HasZoneFlag(zone->GetZoneID())
) {
LogInfo(
"Character [{}] does not have the flag to be in this zone [{}]!",
GetCleanName(),
z->flag_needed
);
return false;
}
}