From 8ef369ee57f018cd9319269ecc25a3cf430e70d7 Mon Sep 17 00:00:00 2001 From: solar Date: Fri, 29 Aug 2025 07:02:57 +0100 Subject: [PATCH] implemented air supply and lung capacity --- zone/bonuses.cpp | 3 ++- zone/client.cpp | 24 ++++++++++++++++++++++-- zone/client.h | 3 +++ zone/client_mods.cpp | 27 +++++++++++++++++++++++++++ zone/client_packet.cpp | 2 -- zone/client_process.cpp | 12 ++++++++++++ zone/common.h | 2 ++ 7 files changed, 68 insertions(+), 5 deletions(-) diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 640e9ddd1..95c05b1d3 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -700,7 +700,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->CHA += base_value; break; case SE_WaterBreathing: - // handled by client + newbon->WaterBreathing = base_value; break; case SE_CurrentMana: newbon->ManaRegen += base_value; @@ -742,6 +742,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) case SE_TwoHandBash: break; case SE_SetBreathLevel: + newbon->BreathLevel += base_value; break; case SE_RaiseStatCap: switch (limit_value) { diff --git a/zone/client.cpp b/zone/client.cpp index 2fa767d09..453971b99 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -189,7 +189,8 @@ Client::Client() : Mob( tmSitting(0), parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)), lazy_load_bank_check_timer(1000), - bandolier_throttle_timer(0) + bandolier_throttle_timer(0), + underwater_timer(1000) { eqs = nullptr; for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) { @@ -497,7 +498,8 @@ Client::Client(EQStreamInterface *ieqs) : Mob( tmSitting(0), parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)), lazy_load_bank_check_timer(1000), - bandolier_throttle_timer(0) + bandolier_throttle_timer(0), + underwater_timer(1000) { for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) { SetFilter(client_filter, FilterShow); @@ -13282,3 +13284,21 @@ bool Client::UncompleteTask(int task_id) return task_state->UncompleteTask(task_id); } + +bool Client::IsUnderWater() +{ + if (!zone->watermap) { + return false; + } + + if (zone->GetZoneID() == Zones::KEDGE) { + return true; + } + + auto underwater = glm::vec3(GetX(), GetY(), GetZ() + GetZOffset()); + if (zone->IsWaterZone(underwater.z) || zone->watermap->InLiquid(underwater)) { + return true; + } + + return false; +} diff --git a/zone/client.h b/zone/client.h index d5e8f3ef4..6ab055d91 100644 --- a/zone/client.h +++ b/zone/client.h @@ -2037,6 +2037,7 @@ private: int64 CalcHPRegen(bool bCombat = false); int64 CalcManaRegen(bool bCombat = false); int64 CalcBaseManaRegen(); + int32 CalculateLungCapacity(); void DoHPRegen(); void DoManaRegen(); void DoStaminaHungerUpdate(); @@ -2215,6 +2216,7 @@ private: Timer parcel_timer; //Used to limit the number of parcels to one every 30 seconds (default). Changable via rule. Timer lazy_load_bank_check_timer; Timer bandolier_throttle_timer; + Timer underwater_timer; bool m_lazy_load_bank = false; int m_lazy_load_sent_bank_slots = 0; @@ -2433,6 +2435,7 @@ public: bool IsFilteredAFKPacket(const EQApplicationPacket *p); void CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p); void SyncWorldPositionsToClient(bool ignore_idle = false); + bool IsUnderWater(); }; #endif diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 23ef13454..9d7b01d84 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1774,3 +1774,30 @@ int Client::GetRawACNoShield(int &shield_ac) const } return ac; } + +int32 Client::CalculateLungCapacity() +{ + // Iksar and Frogloks do not benefit from Innate Lung Capacity and do not have STA penalty + if (GetBaseRace() == IKSAR) return 127; + if (GetBaseRace() == FROGLOK) return 256; + + // Innate Lung Capacity AA gives 10%/25%/50% more + int base_lung_capacity = aabonuses.BreathLevel > 0 ? aabonuses.BreathLevel : 100; + int lung_capacity = base_lung_capacity; + + // having less than 130 STA applies a reduction to air supply, but having more than 130 does not provide a bonus + int STA_penalty = GetSTA() - 30; + if (STA_penalty < 100) { + lung_capacity = base_lung_capacity * STA_penalty / 100; + + if (lung_capacity < 10) { + lung_capacity = 10; + } + + if (lung_capacity > base_lung_capacity) { + lung_capacity = base_lung_capacity; + } + } + + return lung_capacity; +} diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 1f273a191..2d851a0e6 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1707,8 +1707,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) m_pp.abilitySlotRefresh = 0; } - /* Reset to max so they dont drown on zone in if its underwater */ - m_pp.air_remaining = 60; /* Check for PVP Zone status*/ if (zone->IsPVPZone()) m_pp.pvp = 1; diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 99f6a48f5..d6fc6366a 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -223,6 +223,18 @@ bool Client::Process() { if (IsStunned() && stunned_timer.Check()) Mob::UnStun(); + if (underwater_timer.Check()) { + if ((IsUnderWater() || GetZoneID() == Zones::THEGREY) && + !spellbonuses.WaterBreathing && !aabonuses.WaterBreathing && !itembonuses.WaterBreathing) { + if (m_pp.air_remaining > 0) { + --m_pp.air_remaining; + } + } + else { + m_pp.air_remaining = CalculateLungCapacity(); + } + } + cheat_manager.ClientProcess(); if (bardsong_timer.Check() && bardsong != 0) { diff --git a/zone/common.h b/zone/common.h index 5c5e12f79..2212ec3a0 100644 --- a/zone/common.h +++ b/zone/common.h @@ -575,6 +575,8 @@ struct StatBonuses { int aura_slots; int trap_slots; bool hunger; // Song of Sustenance -- min caps to 3500 + int32 BreathLevel; + bool WaterBreathing; int64 heroic_max_hp; int64 heroic_max_mana; int64 heroic_max_end;