/* * This file is a part of the SteamWar software. * * Copyright (C) 2025 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package de.steamwar.lobby.boatrace; import de.steamwar.entity.REntity; import de.steamwar.entity.REntityServer; import de.steamwar.lobby.LobbySystem; import de.steamwar.lobby.util.LeaderboardManager; import de.steamwar.sql.Leaderboard; import de.steamwar.sql.SteamwarUser; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Sound; import org.bukkit.boss.BarColor; import org.bukkit.boss.BarStyle; import org.bukkit.boss.BossBar; import org.bukkit.entity.Boat; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.vehicle.VehicleExitEvent; import org.bukkit.event.vehicle.VehicleMoveEvent; import org.bukkit.scheduler.BukkitTask; import java.util.EventListener; import static de.steamwar.lobby.util.LeaderboardManager.renderTime; public class BoatRace implements EventListener, Listener { private static final String CONFIG_KEY = "lobby@boatrace"; private static final double MIN_HEIGHT = 4.3; public static final REntityServer boatNpcServer; private static boolean oneNotStarted = false; private static final LeaderboardManager leaderboard; static { boatNpcServer = new REntityServer(); REntity starter = new REntity(boatNpcServer, EntityType.VILLAGER, BoatRacePositions.NPC); boatNpcServer.setCallback((player, rEntity, entityAction) -> { if (rEntity != starter) return; Bukkit.getWorlds().get(0).getEntities().stream().filter(entity -> entity.getType() == EntityType.ENDER_CRYSTAL).forEach(Entity::remove); if (entityAction == REntityServer.EntityAction.INTERACT && !oneNotStarted) { oneNotStarted = true; new BoatRace(player); } }); leaderboard = new LeaderboardManager(boatNpcServer, CONFIG_KEY, BoatRacePositions.LEADERBOARD); } private final Player player; private Boat boat; private int nextCheckpoint = 0; private long startTime; private final BukkitTask task; private final BossBar bossBar; private boolean hasBacked = false; private double lastDistance; @EventHandler public void onBoatMove(VehicleMoveEvent event) { if (event.getVehicle() != boat) return; lastDistance = event.getFrom().distance(event.getTo()); if(nextCheckpoint == 0 && inRegion(player, BoatRacePositions.BACKWARDS[0], BoatRacePositions.BACKWARDS[1])) { player.eject(); player.teleport(BoatRacePositions.END); oneNotStarted = false; return; } if(player.getLocation().getY() < MIN_HEIGHT) { Location[] backTo = BoatRacePositions.CHECKPOINTS[nextCheckpoint - 1]; Location avg = new Location(backTo[0].getWorld(), (backTo[0].getX() + backTo[1].getX()) / 2, Math.max(backTo[0].getY(), backTo[1].getY()), (backTo[0].getZ() + backTo[1].getZ()) / 2, backTo[0].getYaw(), backTo[0].getPitch()); Boat nboat = Bukkit.getWorlds().get(0).spawn(avg, Boat.class); nboat.setBoatType(boat.getBoatType()); hasBacked = true; player.eject(); boat.remove(); boat = nboat; boat.addPassenger(player); player.playSound(avg, Sound.ENTITY_ENDERMAN_TELEPORT, 1, 1); return; } Location[] checkpoint = BoatRacePositions.CHECKPOINTS[nextCheckpoint]; if(inRegion(player, checkpoint[0], checkpoint[1])) { if(nextCheckpoint == 0) { oneNotStarted = false; startTime = System.currentTimeMillis(); bossBar.addPlayer(player); bossBar.setVisible(true); } nextCheckpoint++; if (nextCheckpoint == BoatRacePositions.CHECKPOINTS.length) { long time = System.currentTimeMillis() - startTime; boat.remove(); player.eject(); player.teleport(BoatRacePositions.END); player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1, 1); bossBar.removeAll(); HandlerList.unregisterAll(this); task.cancel(); LobbySystem.getMessage().send("BOAT_RACE_TIME", player, renderTime(time)); SteamwarUser user = SteamwarUser.get(player.getUniqueId()); long best = leaderboard.getPlayerTime(user); if (time < best) { LobbySystem.getMessage().send("BOAT_RACE_NEW_BEST", player); leaderboard.updateBestTime(user, time); } } else { player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1); } } } @EventHandler public void onVehicleExit(VehicleExitEvent event) { if (event.getVehicle() != boat) return; if (event.getExited() != player) return; if (hasBacked) return; HandlerList.unregisterAll(this); task.cancel(); bossBar.removeAll(); boat.remove(); player.teleport(BoatRacePositions.END); oneNotStarted = false; } public BoatRace(Player player) { this.player = player; boat = Bukkit.getWorlds().get(0).spawn(BoatRacePositions.START, Boat.class); // boat.setBoatType(Boat.Type.values()[new Random().nextInt(Boat.Type.values().length)]); boat.addPassenger(player); bossBar = Bukkit.createBossBar("", BarColor.BLUE, BarStyle.SOLID); task = Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> { hasBacked = false; if (nextCheckpoint != 0) { double kmh = lastDistance * 20 * 3.6; bossBar.setProgress((nextCheckpoint - 1d) / (BoatRacePositions.CHECKPOINTS.length - 1d)); bossBar.setTitle(LobbySystem.getMessage().parse("BOAT_RACE_TITLE", player, nextCheckpoint, renderTime(System.currentTimeMillis() - startTime), (int) kmh)); } }, 0, 1); Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance()); } private boolean inRegion(Player p, Location loc1, Location loc2) { double x1 = Math.min(loc1.getX(), loc2.getX()); double y1 = Math.min(loc1.getY(), loc2.getY()); double z1 = Math.min(loc1.getZ(), loc2.getZ()); double x2 = Math.max(loc1.getX(), loc2.getX()) + 1; double y2 = Math.max(loc1.getY(), loc2.getY()) + 1; double z2 = Math.max(loc1.getZ(), loc2.getZ()) + 1; return p.getLocation().getX() >= x1 && p.getLocation().getY() >= y1 && p.getLocation().getZ() >= z1 && p.getLocation().getX() < x2 && p.getLocation().getY() < y2 && p.getLocation().getZ() < z2; } }