Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/org/cubexmc/metro/Metro.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ protected void enablePlugin() throws Exception {
getLogger().info("Vault economy not found or disabled.");
}
this.ticketService = new org.cubexmc.metro.service.TicketService(this::getVaultIntegration,
() -> getConfig().getBoolean("economy.enabled", true));
() -> configFacade.isEconomyEnabled());

this.priceService = new org.cubexmc.metro.service.PriceService();
this.lineStatusService = new org.cubexmc.metro.service.LineStatusService(this, lineManager);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ public void reload(CommandSender sender) {
plugin.getPortalManager().load();
}
plugin.getLanguageManager().loadLanguages();
if (plugin.getVaultIntegration() != null) {
plugin.getVaultIntegration().refreshProvider();
}

plugin.refreshMapIntegrations();

Expand Down
17 changes: 11 additions & 6 deletions src/main/java/org/cubexmc/metro/gui/GuiListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public GuiListener(Metro plugin) {

@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
Inventory inv = event.getInventory();
Inventory topInv = event.getView().getTopInventory();

// 检查是否是我们的 GUI
if (!(inv.getHolder() instanceof GuiHolder holder)) {
if (!(topInv.getHolder() instanceof GuiHolder holder)) {
return;
}

Expand All @@ -64,7 +64,7 @@ public void onInventoryClick(InventoryClickEvent event) {
int slot = event.getRawSlot();

// 忽略点击 GUI 外部
if (slot < 0 || slot >= inv.getSize()) {
if (slot < 0 || slot >= topInv.getSize()) {
return;
}

Expand Down Expand Up @@ -94,9 +94,14 @@ public void onInventoryClick(InventoryClickEvent event) {

@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
// 防止在 GUI 中拖拽物品
if (event.getInventory().getHolder() instanceof GuiHolder) {
event.setCancelled(true);
if (event.getView().getTopInventory().getHolder() instanceof GuiHolder) {
int topSize = event.getView().getTopInventory().getSize();
for (int slot : event.getRawSlots()) {
if (slot < topSize) {
event.setCancelled(true);
return;
}
}
}
}
}
Expand Down
42 changes: 34 additions & 8 deletions src/main/java/org/cubexmc/metro/integration/VaultIntegration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,47 @@ import java.util.UUID
import net.milkbowl.vault.economy.Economy
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.plugin.ServiceRegisterEvent
import org.bukkit.plugin.ServiceUnregisterEvent
import org.cubexmc.metro.Metro

class VaultIntegration(private val plugin: Metro) {
class VaultIntegration(private val plugin: Metro) : Listener {
var economy: Economy? = null
private set
var isEnabled: Boolean = setupEconomy()
var isEnabled: Boolean = false
private set

private fun setupEconomy(): Boolean {
if (plugin.server.pluginManager.getPlugin("Vault") == null) {
return false
init {
refreshProvider()
plugin.server.pluginManager.registerEvents(this, plugin)
}

/**
* Re-queries ServicesManager for the current Economy provider.
* Safe to call at any time; does not require the "Vault" plugin to be loaded.
*/
fun refreshProvider() {
val registration = plugin.server.servicesManager.getRegistration(Economy::class.java)
economy = registration?.provider
isEnabled = economy != null
}

@EventHandler(priority = EventPriority.MONITOR)
fun onServiceRegister(event: ServiceRegisterEvent) {
if (event.provider is Economy) {
refreshProvider()
}
}

@EventHandler(priority = EventPriority.MONITOR)
fun onServiceUnregister(event: ServiceUnregisterEvent) {
if (event.provider is Economy) {
economy = null
isEnabled = false
}
val registration = plugin.server.servicesManager.getRegistration(Economy::class.java) ?: return false
economy = registration.provider
return economy != null
}

fun has(player: Player, amount: Double): Boolean {
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/org/cubexmc/metro/listener/VehicleListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,19 @@ public void onVehicleExit(VehicleExitEvent event) {
return;
}

// 玩家下车,清除其界面显示
plugin.getScoreboardManager().clearPlayerDisplay(player);

// 获取当前位置
Location location = minecart.getLocation();

// 检查位置是否在停靠区上,如果不在则禁止下车
if (!isAtStop(location)) {
event.setCancelled(true);
player.sendMessage(plugin.getLanguageManager().getMessage("vehicle.cannot_dismount"));
return;
}

// 玩家下车,清除其界面显示
plugin.getScoreboardManager().clearPlayerDisplay(player);

// 检查位置是否在停靠区上
if (!isAtStop(location)) {
// 如果不在停靠区上,立即移除矿车
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/org/cubexmc/metro/service/TicketService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ class TicketService(
transaction.markCharged()
val owner: UUID? = transaction.line.owner
if (owner != null) {
vault.deposit(owner, transaction.price)
if (!vault.deposit(owner, transaction.price)) {
// Owner deposit failed: refund the passenger and report failure
vault.deposit(transaction.player.uniqueId, transaction.price)
return TicketChargeStatus.TRANSACTION_FAILED
}
}
return TicketChargeStatus.CHARGED
}
Expand Down Expand Up @@ -261,7 +265,11 @@ class TicketService(
}
val owner = line.owner
if (owner != null) {
vault.deposit(owner, priceToCharge)
if (!vault.deposit(owner, priceToCharge)) {
// Owner deposit failed: refund the passenger and report failure
vault.deposit(player.uniqueId, priceToCharge)
return TicketChargeStatus.TRANSACTION_FAILED
}
}
return TicketChargeStatus.CHARGED
}
Expand Down
39 changes: 33 additions & 6 deletions src/main/java/org/cubexmc/metro/train/TrainMovementTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ class TrainMovementTask @JvmOverloads constructor(
movementAssistController.stop()
session.plugin.bedrockCompatibility.onTrainArrival(session.passenger, minecart)

// 先结算里程费用,确保即使 stopPointLocation 为 null 也能正常扣费
settleDistanceFare(stop)

val baseLocation = stop.stopPointLocation ?: return
val snapLocation = baseLocation.clone()
snapLocation.x = snapLocation.blockX + 0.5
Expand All @@ -208,8 +211,6 @@ class TrainMovementTask @JvmOverloads constructor(
session.plugin.routeRecorder.sample(line.id, minecart, snapLocation)
}

settleDistanceFare(stop)

val previousState = stateMachine.transitionTo(TrainState.STOPPED_AT_STATION, null)
if (previousState == TrainState.MOVING_IN_STATION) {
handleArrivalAtStation()
Expand All @@ -231,7 +232,7 @@ class TrainMovementTask @JvmOverloads constructor(
return
}

val variablePrice = when (rule.getMode()) {
val rawVariablePrice = when (rule.getMode()) {
PriceRule.PricingMode.DISTANCE -> distance * rule.getPerBlockRate()
PriceRule.PricingMode.INTERVAL -> {
val intervals = session.plugin.priceService.countStopIntervals(line, session.entryStopId, stop.id)
Expand All @@ -241,23 +242,49 @@ class TrainMovementTask @JvmOverloads constructor(
else -> return
}

if (variablePrice > 0) {
val status = session.plugin.ticketService.chargePrice(passenger, line, variablePrice)
// 应用 maxPrice 上限:整个行程的总费用不得超过 maxPrice
val variablePrice = if (rawVariablePrice > 0) {
val maxPrice = rule.getMaxPrice()
if (maxPrice > 0.0) {
val remaining = maxPrice - rule.getBasePrice() - session.totalVariableCharged
if (remaining <= 0) 0.0 else minOf(rawVariablePrice, remaining)
} else {
rawVariablePrice
}
} else {
0.0
}

// 应用时间折扣
val gameTime = passenger.world?.time ?: 6000L
val discount = rule.getActiveDiscountMultiplier(gameTime)
val finalPrice = if (variablePrice > 0 && discount < 1.0) {
variablePrice * discount
} else {
variablePrice
}

if (finalPrice > 0) {
val status = session.plugin.ticketService.chargePrice(passenger, line, finalPrice)
if (status == TicketService.TicketChargeStatus.CHARGED) {
session.totalVariableCharged += variablePrice // 按上限前价格记录,确保 maxPrice 正确生效
passenger.sendMessage(
session.plugin.languageManager.getMessage(
"economy.paid_distance",
LanguageManager.put(
LanguageManager.args(),
"price",
session.plugin.ticketService.format(variablePrice),
session.plugin.ticketService.format(finalPrice),
),
),
)
}
}

session.addDistance(-distance)

// 更新 entryStopId 为当前站,确保 INTERVAL 模式下下一段区间从当前站开始计算
session.entryStopId = stop.id
}

private fun transitionToMovingInStation(targetStop: Stop) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/cubexmc/metro/train/TrainSession.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class TrainSession(
var entryStopId: String? = currentStopId
var distanceTraveled: Double = 0.0
private set
/** 整个行程中已收取的可变费用总额(不含 basePrice),用于 maxPrice 上限检查 */
var totalVariableCharged: Double = 0.0

fun addDistance(blocks: Double): Double {
distanceTraveled += blocks
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/lang/de_DE.yml
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,8 @@ interact:
vehicle:
terminal_arrived: "<green>Endstation erreicht: <terminal_stop_name>"
auto_depart: "<green>Automatische Abfahrt: von <current_stop_name> nach <next_stop_name>"
passenger_exit: "<gold>Spieler ist ausgestiegen, fährt nicht automatisch ab"
passenger_exit: "<gold>Spieler ist ausgestiegen, fährt nicht automatisch ab"
cannot_dismount: "<red>Du kannst nicht aussteigen, während der Zug fährt!"

chat:
enter_new_name: "<green>Bitte neuen Namen eingeben. Tippe 'cancel' zum Abbrechen."
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/lang/en_US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ vehicle:
terminal_arrived: "<green>Arrived at terminal station: <terminal_stop_name>"
auto_depart: "<green>Auto departure: From <current_stop_name> to <next_stop_name>"
passenger_exit: "<gold>Player has alighted, will not auto depart"
cannot_dismount: "<red>You cannot dismount while the train is moving!"

chat:
enter_new_name: "<green>Please enter the new name. Type 'cancel' to cancel."
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/lang/es_ES.yml
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,8 @@ interact:
vehicle:
terminal_arrived: "<green>Estación terminal alcanzada: <terminal_stop_name>"
auto_depart: "<green>Salida automática: de <current_stop_name> a <next_stop_name>"
passenger_exit: "<gold>El jugador se ha bajado, no saldrá automáticamente"
passenger_exit: "<gold>El jugador se ha bajado, no saldrá automáticamente"
cannot_dismount: "<red>¡No puedes bajarte mientras el tren está en movimiento!"

chat:
enter_new_name: "<green>Por favor ingresa el nuevo nombre. Escribe 'cancel' para abortar."
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/lang/nl_NL.yml
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,8 @@ interact:
vehicle:
terminal_arrived: "<green>Eindstation bereikt: <terminal_stop_name>"
auto_depart: "<green>Automatisch vertrek: van <current_stop_name> naar <next_stop_name>"
passenger_exit: "<gold>Speler is uitgestapt, vertrekt niet automatisch"
passenger_exit: "<gold>Speler is uitgestapt, vertrekt niet automatisch"
cannot_dismount: "<red>Je kunt niet uitstappen terwijl de trein rijdt!"

chat:
enter_new_name: "<green>Voer een nieuwe naam in. Typ 'cancel' om te annuleren."
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/lang/tr_TR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ vehicle:
terminal_arrived: "<green>Son durağa varıldı: <terminal_stop_name>"
auto_depart: "<green>Otomatik kalkış: <current_stop_name> durağından <next_stop_name> durağına"
passenger_exit: "<gold>Oyuncu indi, otomatik kalkış yapılmayacak"
cannot_dismount: "<red>Tren hareket halindeyken inemezsiniz!"

chat:
enter_new_name: "<green>Lütfen yeni adı girin. İptal etmek için 'cancel' yazın."
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/lang/zh_CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ interact:
vehicle:
terminal_arrived: "<green>已到达终点站: <terminal_stop_name>"
auto_depart: "<green>自动发车: 从 <current_stop_name> 前往 <next_stop_name>"
passenger_exit: "<gold>玩家已下车,不会自动发车"
passenger_exit: "<gold>玩家已下车,不会自动发车"
cannot_dismount: "<red>列车行驶中,禁止下车!"

ownership:
server: "服务器"
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/lang/zh_TW.yml
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ vehicle:
terminal_arrived: "<green>已抵達終點站: <terminal_stop_name>"
auto_depart: "<green>自動發車: 從 <current_stop_name> 前往 <next_stop_name>"
passenger_exit: "<gold>玩家已下車,將不會自動發車"
cannot_dismount: "<red>列車行駛中,禁止下車!"

ownership:
server: "伺服器"
Expand Down
Loading
Loading