This commit is contained in:
okx-code
2026-05-13 22:16:20 +01:00
parent f60312a5c9
commit dcfb00ea30
4 changed files with 295 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
package net.civmc.zorweth;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.block.BlockState;
import java.util.logging.Level;
import net.civmc.zorweth.transfer.DestinationRocketTransfer;
import net.civmc.zorweth.transfer.RocketBlockPosition;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.Dispenser;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.persistence.PersistentDataType;
public final class DestinationTransferListener implements Listener {
private final ZorwethPlugin plugin;
public DestinationTransferListener(final ZorwethPlugin plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
public void onPlayerJoin(final PlayerJoinEvent event) {
final Player player = event.getPlayer();
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> handleJoin(player));
}
private void handleJoin(final Player player) {
final DestinationRocketTransfer transfer;
try {
transfer = this.plugin.getRocketTransferDao().getPendingDestinationTransfer(
player.getUniqueId(), this.plugin.getServerName());
} catch (final Exception exception) {
this.plugin.getLogger().log(Level.SEVERE, "Failed to look up destination rocket transfer", exception);
kick(player);
return;
}
if (transfer == null) {
return;
}
final boolean claimed;
try {
claimed = this.plugin.getRocketTransferDao().claimDestinationTransfer(transfer.transferId(), player.getUniqueId());
} catch (final Exception exception) {
this.plugin.getLogger().log(Level.SEVERE, "Failed to claim destination rocket transfer", exception);
kick(player);
return;
}
if (!claimed) {
kick(player);
return;
}
Bukkit.getScheduler().runTask(this.plugin, () -> {
try {
if (!ensureDestinationRocketPasted(transfer)) {
kick(player);
}
} catch (final RuntimeException exception) {
this.plugin.getLogger().log(Level.SEVERE, "Failed to paste destination rocket", exception);
kick(player);
}
});
}
private boolean ensureDestinationRocketPasted(final DestinationRocketTransfer transfer) {
final World world = Bukkit.getWorld(transfer.destinationWorld());
if (world == null) {
this.plugin.getLogger().warning("Destination world is not loaded: " + transfer.destinationWorld());
return false;
}
final Block computer = getDestinationComputerBlock(world, transfer.destinationOrigin());
final String existingTransferId = getSchematicPasteMarker(computer);
if (transfer.transferId().toString().equals(existingTransferId)) {
return true;
}
if (existingTransferId != null) {
this.plugin.getLogger().warning("Destination rocket already has schematic marker for " + existingTransferId
+ " at " + computer.getLocation());
return false;
}
pasteRocket(world, transfer.destinationOrigin());
markDestinationComputer(getDestinationComputerBlock(world, transfer.destinationOrigin()), transfer);
return true;
}
private String getSchematicPasteMarker(final Block computer) {
if (computer.getType() != Material.DISPENSER) {
return null;
}
final Dispenser dispenser = (Dispenser) computer.getState(false);
return dispenser.getPersistentDataContainer().get(RocketTransferKeys.SCHEMATIC_PASTED_LOCAL,
PersistentDataType.STRING);
}
private void pasteRocket(final World world, final RocketBlockPosition origin) {
final Clipboard clipboard = this.plugin.getRocketClipboard();
final Region region = clipboard.getRegion();
final BlockVector3 schematicNorthWestCorner = region.getMinimumPoint();
for (final BlockVector3 position : region) {
final BlockVector3 relative = position.subtract(schematicNorthWestCorner);
final BlockState block = clipboard.getBlock(position);
final Block target = world.getBlockAt(
origin.x() + relative.getX(),
origin.y() + relative.getY(),
origin.z() + relative.getZ()
);
target.setBlockData(Bukkit.createBlockData(block.getAsString()), false);
}
}
private void markDestinationComputer(final Block computer, final DestinationRocketTransfer transfer) {
if (computer.getType() != Material.DISPENSER) {
throw new IllegalStateException("Destination schematic did not produce a flight computer at "
+ computer.getLocation());
}
final Dispenser dispenser = (Dispenser) computer.getState(false);
dispenser.getPersistentDataContainer().set(FlightComputer.ROCKET_COMPUTER_KEY, PersistentDataType.BOOLEAN, true);
dispenser.getPersistentDataContainer().set(RocketTransferKeys.DESTINATION_TRANSFER_ID,
PersistentDataType.STRING, transfer.transferId().toString());
dispenser.getPersistentDataContainer().set(RocketTransferKeys.SCHEMATIC_PASTED_LOCAL,
PersistentDataType.STRING, transfer.transferId().toString());
dispenser.update(true, false);
}
private Block getDestinationComputerBlock(final World world, final RocketBlockPosition origin) {
return world.getBlockAt(
origin.x() + FlightComputer.RELATIVE_POSITION.getX(),
origin.y() + FlightComputer.RELATIVE_POSITION.getY(),
origin.z() + FlightComputer.RELATIVE_POSITION.getZ()
);
}
private void kick(final Player player) {
Bukkit.getScheduler().runTask(this.plugin, () -> {
if (player.isOnline()) {
player.kick(Component.text(this.plugin.getTransferFailureMessage(), NamedTextColor.RED));
}
});
}
}

View File

@@ -48,6 +48,7 @@ public final class ZorwethPlugin extends JavaPlugin {
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
this.rocketClipboard = loadRocketClipboard();
getServer().getPluginManager().registerEvents(new FlightComputer(this), this);
getServer().getPluginManager().registerEvents(new DestinationTransferListener(this), this);
}
@Override

View File

@@ -2,8 +2,11 @@ package net.civmc.zorweth.database;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
import javax.sql.DataSource;
import net.civmc.zorweth.transfer.DestinationRocketTransfer;
import net.civmc.zorweth.transfer.RocketBlockPosition;
import net.civmc.zorweth.transfer.RocketChestTransfer;
import net.civmc.zorweth.transfer.RocketManifest;
@@ -54,6 +57,69 @@ public final class RocketTransferDao {
}
}
public DestinationRocketTransfer getPendingDestinationTransfer(final UUID playerUuid, final String destinationServer)
throws SQLException {
try (Connection connection = this.dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement("""
SELECT rt.transfer_id, rt.state, rt.destination_world,
rt.destination_origin_x, rt.destination_origin_y, rt.destination_origin_z
FROM rocket_transfer_players rtp
JOIN rocket_transfers rt ON rt.transfer_id = rtp.transfer_id
WHERE rtp.player_uuid = ?
AND rt.destination_server = ?
AND rt.state IN ('SOURCE_CLEARED', 'CLAIMED')
AND rtp.state IN ('PENDING', 'CLAIMED')
ORDER BY rt.created_at DESC
LIMIT 1
""")) {
statement.setString(1, playerUuid.toString());
statement.setString(2, destinationServer);
try (ResultSet resultSet = statement.executeQuery()) {
if (!resultSet.next()) {
return null;
}
return new DestinationRocketTransfer(
UUID.fromString(resultSet.getString("transfer_id")),
RocketTransferState.valueOf(resultSet.getString("state")),
resultSet.getString("destination_world"),
new RocketBlockPosition(
resultSet.getInt("destination_origin_x"),
resultSet.getInt("destination_origin_y"),
resultSet.getInt("destination_origin_z")
)
);
}
}
}
public boolean claimDestinationTransfer(final UUID transferId, final UUID playerUuid) throws SQLException {
try (Connection connection = this.dataSource.getConnection()) {
connection.setAutoCommit(false);
try {
final RocketTransferState transferState = getTransferStateForUpdate(connection, transferId);
if (transferState == null || (transferState != RocketTransferState.SOURCE_CLEARED
&& transferState != RocketTransferState.CLAIMED)) {
connection.rollback();
return false;
}
if (transferState == RocketTransferState.SOURCE_CLEARED) {
updateTransferState(connection, transferId, RocketTransferState.CLAIMED);
}
if (!claimPassenger(connection, transferId, playerUuid)) {
connection.rollback();
return false;
}
connection.commit();
return true;
} catch (final SQLException exception) {
connection.rollback();
throw exception;
} finally {
connection.setAutoCommit(true);
}
}
}
private void insertTransfer(final Connection connection, final RocketManifest manifest,
final RocketBlockPosition destinationOrigin) throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("""
@@ -82,6 +148,61 @@ public final class RocketTransferDao {
}
}
private RocketTransferState getTransferStateForUpdate(final Connection connection, final UUID transferId)
throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("""
SELECT state
FROM rocket_transfers
WHERE transfer_id = ?
FOR UPDATE
""")) {
statement.setString(1, transferId.toString());
try (ResultSet resultSet = statement.executeQuery()) {
if (!resultSet.next()) {
return null;
}
return RocketTransferState.valueOf(resultSet.getString("state"));
}
}
}
private void updateTransferState(final Connection connection, final UUID transferId, final RocketTransferState state)
throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("""
UPDATE rocket_transfers
SET state = ?
WHERE transfer_id = ?
""")) {
statement.setString(1, state.name());
statement.setString(2, transferId.toString());
statement.executeUpdate();
}
}
private boolean claimPassenger(final Connection connection, final UUID transferId, final UUID playerUuid)
throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("""
UPDATE rocket_transfer_players
SET state = 'CLAIMED'
WHERE transfer_id = ? AND player_uuid = ? AND state IN ('PENDING', 'CLAIMED')
""")) {
statement.setString(1, transferId.toString());
statement.setString(2, playerUuid.toString());
statement.executeUpdate();
}
try (PreparedStatement statement = connection.prepareStatement("""
SELECT 1
FROM rocket_transfer_players
WHERE transfer_id = ? AND player_uuid = ? AND state = 'CLAIMED'
""")) {
statement.setString(1, transferId.toString());
statement.setString(2, playerUuid.toString());
try (ResultSet resultSet = statement.executeQuery()) {
return resultSet.next();
}
}
}
private void insertPassengers(final Connection connection, final Iterable<RocketPassengerTransfer> passengers)
throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("""

View File

@@ -0,0 +1,19 @@
package net.civmc.zorweth.transfer;
import java.util.Objects;
import java.util.UUID;
public record DestinationRocketTransfer(
UUID transferId,
RocketTransferState state,
String destinationWorld,
RocketBlockPosition destinationOrigin
) {
public DestinationRocketTransfer {
Objects.requireNonNull(transferId, "transferId");
Objects.requireNonNull(state, "state");
Objects.requireNonNull(destinationWorld, "destinationWorld");
Objects.requireNonNull(destinationOrigin, "destinationOrigin");
}
}