feat: optional property rent

Off unless property_rent_enabled is on. Each property world's rent comes from its PropertyTemplate
row (minimumPrice every rentDuration x durationType; Block Yard is free), unless the new Property
Rent dashboard page sets another price or period (property_rent_manage). Rent is taken from the
owner's coins shortly after their character loads, with a mail receipt; unpaid rent is mailed and,
after property_rent_grace_days, makes the property private until it is paid, like live. The
property management component refuses public or best friends privacy while rent is overdue and a
property world that loads overdue makes itself private. Property game messages are unchanged.

Fixes #943

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-26 23:29:57 -05:00
parent b2403b09ea
commit 680615ba9f
31 changed files with 784 additions and 3 deletions

View File

@@ -1,4 +1,6 @@
#include "PropertyManagementComponent.h"
#include "PropertyRent.h"
#include "ChatPackets.h"
#include "DashboardNotify.h"
#include <sstream>
@@ -77,6 +79,13 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent, const i
this->claimedTime = propertyInfo->claimedTime;
this->reputation = propertyInfo->reputation;
// Rent that went unpaid while the owner was away makes the property private
if (privacyOption != PropertyPrivacyOption::Private && PropertyRent::IsOverdue(propertyId, zoneId)) {
privacyOption = PropertyPrivacyOption::Private;
Database::Get()->SetPropertyPrivacy(propertyId, static_cast<int32_t>(privacyOption));
LOG("Property %llu is private: its rent is unpaid", propertyId);
}
Load();
}
}
@@ -134,6 +143,13 @@ void PropertyManagementComponent::SetPrivacyOption(PropertyPrivacyOption value)
value = PropertyPrivacyOption::Private;
}
// Unpaid rent keeps the property private, like live
if (value != PropertyPrivacyOption::Private && PropertyRent::IsOverdue(propertyId, Game::zoneManager->GetZoneID().GetMapID())) {
value = PropertyPrivacyOption::Private;
auto* ownerEntity = GetOwner();
if (ownerEntity) ChatPackets::SendSystemMessage(ownerEntity->GetSystemAddress(), u"Your property's rent is unpaid, so it stays private until the rent is paid.");
}
if (value == PropertyPrivacyOption::Public && privacyOption != PropertyPrivacyOption::Public) {
rejectionReason = "";
moderatorRequested = true;