Skip to content

Commit 332ee16

Browse files
committed
Disallow unrent if the landlord doesn't have enough money
1 parent a92034f commit 332ee16

2 files changed

Lines changed: 84 additions & 43 deletions

File tree

realty-common/src/main/java/io/github/md5sha256/realty/database/RealtyLogicImpl.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,22 @@ record NoLeaseholdContract() implements UnrentResult {}
700700
record UpdateFailed() implements UnrentResult {}
701701
}
702702

703+
public @NotNull UnrentResult previewUnrent(@NotNull String worldGuardRegionId,
704+
@NotNull UUID worldId) {
705+
try (SqlSessionWrapper wrapper = database.openSession()) {
706+
LeaseholdContractMapper leaseholdMapper = wrapper.leaseholdContractMapper();
707+
LeaseholdContractEntity lease = leaseholdMapper.selectByRegion(worldGuardRegionId, worldId);
708+
if (lease == null) {
709+
return new UnrentResult.NoLeaseholdContract();
710+
}
711+
long totalSeconds = lease.durationSeconds();
712+
long remainingSeconds = lease.endDate() == null ? 0
713+
: Math.max(0, java.time.Duration.between(java.time.LocalDateTime.now(), lease.endDate()).getSeconds());
714+
double refund = totalSeconds > 0 ? lease.price() * remainingSeconds / totalSeconds : 0;
715+
return new UnrentResult.Success(refund, lease.tenantId(), lease.landlordId());
716+
}
717+
}
718+
703719
public @NotNull UnrentResult unrentRegion(@NotNull String worldGuardRegionId,
704720
@NotNull UUID worldId,
705721
@NotNull UUID tenantId) {

realty-paper/src/main/java/io/github/md5sha256/realty/command/UnrentCommand.java

Lines changed: 68 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -71,60 +71,85 @@ private void execute(@NotNull CommandContext<CommandSourceStack> ctx) {
7171
Placeholder.unparsed("region", regionId)));
7272
return;
7373
}
74+
// Step 1: preview (DB read, no mutation)
7475
CompletableFuture.supplyAsync(() -> {
7576
try {
76-
RealtyLogicImpl.UnrentResult result = logic.unrentRegion(
77-
regionId, region.world().getUID(), sender.getUniqueId());
78-
return switch (result) {
79-
case RealtyLogicImpl.UnrentResult.Success success -> {
80-
Map<String, String> placeholders = logic.getRegionPlaceholders(regionId, region.world().getUID());
81-
yield Map.entry(success, placeholders);
82-
}
83-
case RealtyLogicImpl.UnrentResult.NoLeaseholdContract ignored -> {
84-
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_NO_LEASEHOLD_CONTRACT,
85-
Placeholder.unparsed("region", regionId)));
86-
yield null;
87-
}
88-
case RealtyLogicImpl.UnrentResult.UpdateFailed ignored -> {
89-
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_UPDATE_FAILED,
90-
Placeholder.unparsed("region", regionId)));
91-
yield null;
92-
}
93-
};
77+
return logic.previewUnrent(regionId, region.world().getUID());
9478
} catch (Exception ex) {
9579
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_ERROR,
9680
Placeholder.unparsed("error", ex.getMessage())));
9781
return null;
9882
}
99-
}, executorState.dbExec()).thenAcceptAsync(entry -> {
100-
if (entry == null) {
83+
}, executorState.dbExec()).thenAcceptAsync(preview -> {
84+
if (preview == null) {
10185
return;
10286
}
103-
RealtyLogicImpl.UnrentResult.Success success = entry.getKey();
104-
double refund = success.refund();
105-
if (refund > 0) {
106-
EconomyResponse response = economy.depositPlayer(sender, refund);
107-
if (!response.transactionSuccess()) {
108-
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_REFUND_FAILED,
109-
Placeholder.unparsed("error", response.errorMessage)));
110-
return;
87+
switch (preview) {
88+
case RealtyLogicImpl.UnrentResult.NoLeaseholdContract ignored ->
89+
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_NO_LEASEHOLD_CONTRACT,
90+
Placeholder.unparsed("region", regionId)));
91+
case RealtyLogicImpl.UnrentResult.UpdateFailed ignored ->
92+
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_UPDATE_FAILED,
93+
Placeholder.unparsed("region", regionId)));
94+
case RealtyLogicImpl.UnrentResult.Success success -> {
95+
double refund = success.refund();
96+
// Step 2: economy — withdraw from landlord, deposit to tenant (main thread)
97+
if (refund > 0) {
98+
OfflinePlayer landlord = Bukkit.getOfflinePlayer(success.landlordId());
99+
EconomyResponse withdrawResponse = economy.withdrawPlayer(landlord, refund);
100+
if (!withdrawResponse.transactionSuccess()) {
101+
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_REFUND_FAILED,
102+
Placeholder.unparsed("error", withdrawResponse.errorMessage)));
103+
return;
104+
}
105+
EconomyResponse depositResponse = economy.depositPlayer(sender, refund);
106+
if (!depositResponse.transactionSuccess()) {
107+
economy.depositPlayer(landlord, refund);
108+
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_REFUND_FAILED,
109+
Placeholder.unparsed("error", depositResponse.errorMessage)));
110+
return;
111+
}
112+
}
113+
// Step 3: DB mutation
114+
CompletableFuture.supplyAsync(() -> {
115+
try {
116+
RealtyLogicImpl.UnrentResult result = logic.unrentRegion(
117+
regionId, region.world().getUID(), sender.getUniqueId());
118+
if (result instanceof RealtyLogicImpl.UnrentResult.Success) {
119+
return logic.getRegionPlaceholders(regionId, region.world().getUID());
120+
}
121+
return null;
122+
} catch (Exception ex) {
123+
return null;
124+
}
125+
}, executorState.dbExec()).thenAcceptAsync(placeholders -> {
126+
// Step 4: finalize or revert
127+
if (placeholders == null) {
128+
if (refund > 0) {
129+
economy.withdrawPlayer(sender, refund);
130+
OfflinePlayer landlord = Bukkit.getOfflinePlayer(success.landlordId());
131+
economy.depositPlayer(landlord, refund);
132+
}
133+
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_UPDATE_FAILED,
134+
Placeholder.unparsed("region", regionId)));
135+
return;
136+
}
137+
ProtectedRegion protectedRegion = region.region();
138+
protectedRegion.getOwners().clear();
139+
protectedRegion.getMembers().clear();
140+
regionProfileService.applyFlags(region, RegionState.FOR_LEASE, placeholders);
141+
signTextApplicator.updateLoadedSigns(region.world(), regionId, RegionState.FOR_LEASE, placeholders);
142+
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_SUCCESS,
143+
Placeholder.unparsed("region", regionId),
144+
Placeholder.unparsed("refund", CurrencyFormatter.format(refund))));
145+
notificationService.queueNotification(success.landlordId(),
146+
messages.messageFor(MessageKeys.NOTIFICATION_REGION_UNRENTED,
147+
Placeholder.unparsed("player", sender.getName()),
148+
Placeholder.unparsed("region", regionId),
149+
Placeholder.unparsed("refund", CurrencyFormatter.format(refund))));
150+
}, executorState.mainThreadExec());
111151
}
112-
OfflinePlayer landlord = Bukkit.getOfflinePlayer(success.landlordId());
113-
economy.withdrawPlayer(landlord, refund);
114152
}
115-
ProtectedRegion protectedRegion = region.region();
116-
protectedRegion.getOwners().clear();
117-
protectedRegion.getMembers().clear();
118-
regionProfileService.applyFlags(region, RegionState.FOR_LEASE, entry.getValue());
119-
signTextApplicator.updateLoadedSigns(region.world(), regionId, RegionState.FOR_LEASE, entry.getValue());
120-
sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_SUCCESS,
121-
Placeholder.unparsed("region", regionId),
122-
Placeholder.unparsed("refund", CurrencyFormatter.format(refund))));
123-
notificationService.queueNotification(success.landlordId(),
124-
messages.messageFor(MessageKeys.NOTIFICATION_REGION_UNRENTED,
125-
Placeholder.unparsed("player", sender.getName()),
126-
Placeholder.unparsed("region", regionId),
127-
Placeholder.unparsed("refund", CurrencyFormatter.format(refund))));
128153
}, executorState.mainThreadExec());
129154
}
130155

0 commit comments

Comments
 (0)