mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-10 23:03:44 +00:00
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:
@@ -57,6 +57,11 @@ namespace HotPropertySlots {
|
||||
uint32_t id{};
|
||||
uint32_t mapId{};
|
||||
std::string spawnName;
|
||||
// Rent (PropertyRent.h) and reputation (PropertyReputation.h) of the template
|
||||
int64_t minimumPrice{};
|
||||
int32_t rentDuration{};
|
||||
int32_t durationType{};
|
||||
int32_t reputationPerMinute{};
|
||||
};
|
||||
|
||||
struct EntranceRow { // PropertyEntranceComponent
|
||||
@@ -112,6 +117,24 @@ namespace HotPropertySlots {
|
||||
return worlds;
|
||||
}
|
||||
|
||||
/**
|
||||
* The PropertyTemplate row a property world uses: the lowest id among its rows a property entrance leads to (as
|
||||
* PropertyWorlds), or its lowest id row when no entrance leads there. nullptr when the map has no row.
|
||||
*/
|
||||
inline const TemplateRow* WorldTemplate(const std::vector<TemplateRow>& templates, const std::vector<EntranceRow>& entrances, uint32_t mapId) {
|
||||
const TemplateRow* entered = nullptr;
|
||||
const TemplateRow* any = nullptr;
|
||||
for (const auto& row : templates) {
|
||||
if (row.mapId != mapId) continue;
|
||||
if (!any || row.id < any->id) any = &row;
|
||||
const bool isEntered = std::any_of(entrances.begin(), entrances.end(), [&](const EntranceRow& entrance) {
|
||||
return entrance.mapId == row.mapId && entrance.propertyName == row.spawnName;
|
||||
});
|
||||
if (isEntered && (!entered || row.id < entered->id)) entered = &row;
|
||||
}
|
||||
return entered ? entered : any;
|
||||
}
|
||||
|
||||
// A slot's location as stored (0: none) if it is still a property world, else the slot's own world
|
||||
inline uint32_t Location(uint32_t stored, const Slot& slot, const std::vector<uint32_t>& worlds) {
|
||||
return stored != 0 && std::find(worlds.begin(), worlds.end(), stored) != worlds.end() ? stored : slot.mapId;
|
||||
|
||||
@@ -86,6 +86,7 @@ namespace {
|
||||
{ "reports_review_flags", "Economy and map", "Review flags", "Mark economy flags dismissed or actioned", 3 },
|
||||
{ "items_restore", "Economy and map", "Give items back", "Mail a traced item back to a player, or what a character lost since one of its snapshots (with original IDs when they no longer exist)", 8 },
|
||||
{ "reports_run_checks", "Economy and map", "Run economy checks", "Run the anomaly checks by hand", 8 },
|
||||
{ "property_rent_manage", "Economy and map", "Property rent", "Change the rent of property worlds on the Property Rent page", 8 },
|
||||
{ "contraband_manage", "Economy and map", "Contraband list", "Add, change and remove contraband items (flagged, or removed from players, when a character has one)", 8 },
|
||||
|
||||
{ "play_keys_manage", "Server", "Play keys", "Create, edit and delete play keys, and see the key an account used", 8 },
|
||||
|
||||
85
dCommon/PropertyRentRules.h
Normal file
85
dCommon/PropertyRentRules.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#ifndef __PROPERTYRENTRULES__H__
|
||||
#define __PROPERTYRENTRULES__H__
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* Property rent (issue #943), the rules without the game around them so they can be unit tested. Off unless
|
||||
* property_rent_enabled is on. A property world's rent comes from its PropertyTemplate row (minimumPrice coins every
|
||||
* rentDuration x durationType), unless the dashboard's Property Rent page sets another price or period for the world.
|
||||
* A price of 0 (Block Yard's) means the property is free.
|
||||
*
|
||||
* Rent is charged when the owner's character loads in any world. Paying moves the due date one period on from now
|
||||
* (missed periods are not charged afterwards). If the owner can't pay, the rent is overdue: after the grace period
|
||||
* the property is made private and can't be made public (or best friends only) again until the rent is paid, like
|
||||
* live. Nothing on the property changes.
|
||||
*/
|
||||
namespace PropertyRentRules {
|
||||
constexpr int64_t DAY = 24 * 60 * 60;
|
||||
|
||||
struct Rate {
|
||||
int64_t price{}; // coins per period; 0: free
|
||||
int64_t periodSeconds{};
|
||||
bool operator==(const Rate&) const = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* PropertyTemplate's durationType as seconds: 1 is days (the test templates rent for 7 of them) and 6 is months
|
||||
* (every property world rents for 1, and live charged monthly), counted as 30 days. The client only shows the
|
||||
* number (UI_PROPERTY_LEASE_VALUE: "%(every) %(count) %(timeUnits)"), so other values are read as months too.
|
||||
*/
|
||||
inline int64_t DurationUnit(int32_t durationType) {
|
||||
return durationType == 1 ? DAY : 30 * DAY;
|
||||
}
|
||||
|
||||
// The template's rent; nullopt when it has none (no price, or no period)
|
||||
inline std::optional<Rate> TemplateRate(int64_t minimumPrice, int32_t rentDuration, int32_t durationType) {
|
||||
if (minimumPrice <= 0 || rentDuration <= 0) return std::nullopt;
|
||||
return Rate{ minimumPrice, static_cast<int64_t>(rentDuration) * DurationUnit(durationType) };
|
||||
}
|
||||
|
||||
// A dashboard override (price 0 makes the world free); a period of 0 days keeps the template's
|
||||
inline std::optional<Rate> Resolve(const std::optional<Rate>& fromTemplate, std::optional<int64_t> overridePrice, std::optional<int64_t> overrideDays) {
|
||||
if (!overridePrice && !overrideDays) return fromTemplate;
|
||||
const int64_t price = overridePrice.value_or(fromTemplate ? fromTemplate->price : 0);
|
||||
int64_t period = overrideDays && *overrideDays > 0 ? *overrideDays * DAY : (fromTemplate ? fromTemplate->periodSeconds : 30 * DAY);
|
||||
if (price <= 0 || period <= 0) return std::nullopt;
|
||||
return Rate{ price, period };
|
||||
}
|
||||
|
||||
enum class eOutcome : uint8_t {
|
||||
NOT_DUE, // nothing to do
|
||||
PAID, // charge `charge` coins; the next rent is due at newDue
|
||||
UNPAID, // due but the owner can't pay; overdue says whether the grace period is over
|
||||
};
|
||||
|
||||
struct Decision {
|
||||
eOutcome outcome{};
|
||||
int64_t charge{};
|
||||
int64_t newDue{};
|
||||
bool overdue{};
|
||||
bool operator==(const Decision&) const = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* What to do when the owner loads. due: when the rent is due (0: never charged yet, due now); coins: what the owner
|
||||
* has; grace: seconds after the due date before an unpaid property is made private.
|
||||
*/
|
||||
inline Decision Decide(const std::optional<Rate>& rate, int64_t due, int64_t now, int64_t coins, int64_t grace) {
|
||||
if (!rate || rate->price <= 0) return { eOutcome::NOT_DUE, 0, due, false };
|
||||
if (due > now) return { eOutcome::NOT_DUE, 0, due, false };
|
||||
if (coins >= rate->price) return { eOutcome::PAID, rate->price, now + rate->periodSeconds, false };
|
||||
// Never charged before counts as due from now, so the grace period starts now
|
||||
const int64_t dueFrom = due == 0 ? now : due;
|
||||
return { eOutcome::UNPAID, 0, dueFrom, now >= dueFrom + std::max<int64_t>(grace, 0) };
|
||||
}
|
||||
|
||||
// Whether an unpaid property must be private: rent that was due (a charge was attempted) and the grace period is over
|
||||
inline bool IsOverdue(const std::optional<Rate>& rate, int64_t due, int64_t now, int64_t grace) {
|
||||
return rate && rate->price > 0 && due > 0 && now >= due + std::max<int64_t>(grace, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //!__PROPERTYRENTRULES__H__
|
||||
Reference in New Issue
Block a user